/documents/npush-versions/v2/npush-s-v2.io.js
JavaScript | 1629 lines | 872 code | 398 blank | 359 comment | 172 complexity | ed60d4a5f64a661d224b4c2aed978d49 MD5 | raw file
- /*! NEWICON NPUSH SERVER build: 2.0.0 , development. Copyright(c) 2012 NEWICON LTD */
- /*
- * !!! CURRENTLY IN DEVELOPMENT !!!
- */
- /*
- * Proper command line to launch n:push
- *
- * Example command:
- * $ NODE_ENV=production node n-push-s.js 8080 10
- *
- * Description:
- * $ -environement- -node- -script_name- -port- -instances-
- */
- /*
- * TO DO
- * ok - optimize users list for app/channel/room - probably best solution is send
- * changes only for user subscribed for path /service/activity/users
- * ok - optimize api activity, maybe the best is send to /service/activity/api
- * ok - add activity for server, path should be /service/activity/system
- * ok - delete user from nicnames object when disconnected,
- * clear also not active api/channel/room from nicknames object
- * - add messeage-read event as an option in npush library
- * - merge 3 different function for API POST (brodcast, channel, room)
- * to one function
- * - api activity - calculate incoming and outgoing traffic
- * ok - add active sockets count to message - will be used in receipts area
- * - maybe good idea is create array like nicnames but with sockets - it could
- * imprvoe sendind messages to socket in app or in channel or in room
- * ok - on connect we should delete event appusers and channelusers. Only room
- * users should be still. appusers and channelusers should be moved to
- * apiactivty channel stats!
- * - add function to calculate number of sockets in api/channel/rooms -> should
- * be updated after each connect and disconnect
- * - add activity metter when send messages for public_activity history
- * - public_activity - messages history - ability to get messages
- * with recepit as option. the same functionality to public_activity and
- * public_activity_ms
- * - public_activiyt_XXX - optimize this functions, now both are too big
- * - new EVENT to get receipt for passed message_id
- * - probably good idea is handle event for each socket depends on events handle
- * when channel is created on client side. For this, we have to pass list
- * of events with data to hanfdhake process. if handshaked then we can
- * create event callback for user list. NOW ALL EVENTS ARE ALWAYS CREATED.
- * ok - dont send list of users every second. send only if something was chnaged
- * - size of nicknames array
- * - size of activity array
- * - add console start up arguments:
- *
- * npush-port
- * number-of-instances to run
- * mongo-adress:port:username:password:db
- * redis-store-address:port:password
- *
- * - MULTI INSTANCES PROBLEMS TO SOLVE
- * ok - now we have scokets belongs to API on diffrent node processes, so to calculate
- * statistics we have to send stats to MASTER, MERGE data and then send stats
- * to SLAVES; info we need are sockets, dbs
- * - important - data should be calculated and send only if any api_key/npush/*
- * channel exist
- *
- * BUGS
- * ok - on user connect the new users list is send to all api sockets. Should be
- * send only to sockets in api of joined user
- * - when looking for rooms name in socket.manager.rooms using _string.substr
- * need to check also length of string. now /service and service1
- * is the same for that method and it is really wrong
- *
- * SCARY
- * - when users receive list of user it generate huge traffic for each
- * user authorisation and disconnection, reconnect etc.
- * IT MUST BE AS OPTION AND ONLY FOR SPECIAL SERVICE CHANNEL
- * ok - doconnected user from room dont delete channel/room from history. IT IS THE
- * REASON WHY AFTER MANY CONNECTION AND DISCONNECTION ALL GO SLOWER...
- */
- /**
- * Krzysztof Stasiak
- * npush-s.js
- * Copyright(c) 2012 NEWICON LTD <newicon@newicon.net>
- * GPL Licensed
- */
- var cluster = require('cluster');
- var http = require('http');
- var startInstancesNumber = (process.argv[3]) || 1;
- /**
- * APIs Activity Manager
- *
- * Calcualte API activity and send information if any socket
- * handle that event.
- *
- * @param object socket
- */
- function PusherAPIStatsRecordCreate(socket)
- {
- return {
- s:{ // summary
- m:0, // m -> messages count
- pm:0, // pm -> messages count 1 second before
- mps:0, // mps -> messages per second
- mpm:0, // mpm -> messages per minute
- mph:0, // mph -> messages per hour
- dbrps:0, // dbr -> db reads per second
- dbwps:0, // dbw -> db writes per second
- dbups:0 // dbu -> db updates per second
- },
- t:{ // temp history
- mh: new Array(), // last 60 second history
- mhp: 0, // last 60 second history pointer
- hh: new Array(), // last 60 minute history
- hhp: 0 // last 60 minute history pointer
- },
- c:{ // connection
- s:false, // new list is waiting for send
- a:true // allow traffic
- },
- n:{ // npush
- as:0, // active sockets
- n:'n/a' // client name
- }
- };
- }
- /**
- * Master Server Instance
- *
- * 1. Master instance forks workers up to startInstancesNumber number.
- * 2. Master process receive worker messages and send own to workers.
- * 3. Master process manage load-balancer of TCP traffic in cluster.
- */
- if(cluster.isMaster) {
-
- // this object should keep all statistics sockets, db, process data for
- // all running instaneces
- var stats = {
- w:new Array(), // worker PID list
- a:new Object(), // activity for all socket
- n:new Object(), // global nicknames
- t:{ // traffic
- a:true // allow traffic
- }
- };
-
- // this object contain activity from all workers
- var activityGlobal = new Array();
- var nicknameGlobal = new Array();
-
- // do this when worker receive a message
- function messageHandler(data) {
-
- if(data.event == 'worker-activity') {
- // get feedback from worker if master send 'send-activity' event
- for(s in data.data.a) {
-
- if(!activityGlobal[s])
- activityGlobal[s] = new Array();
-
- activityGlobal[s][this.id] = data.data.a[s];
- }
-
- // merge nickanmes for all workers
- for(a in data.data.n) {
- for(c in data.data.n[a]) {
- for(r in data.data.n[a][c]) {
- for(u in data.data.n[a][c][r]) {
-
- if(!stats.n[a]) stats.n[a] = new Object();
- if(!stats.n[a][c]) stats.n[a][c] = new Object();
- if(!stats.n[a][c][r]) stats.n[a][c][r] = new Object();
- stats.n[a][c][r][u] = data.data.n[a][c][r][u];
- }
- }
- }
- }
- } else if(data.event == 'system-command') {
- if(data.data.action) {
- if(data.data.action == 'traffic') {
- if(data.data.value == '0') {
- stats.t.a = false;
- } else if(data.data.value == '1') {
- stats.t.a = true;
- }
- }
- }
- }
- }
-
- // create workers process
- for (var i = 0; i < startInstancesNumber; i++) {
- cluster.fork();
- }
-
- // listen for worker messages
- Object.keys(cluster.workers).forEach(function(id) {
-
- stats.w.push(cluster.workers[id].process.pid);
- cluster.workers[id].on('message', messageHandler);
-
- });
- // do this when worker died
- cluster.on('exit', function(worker, code, signal) {
-
- console.log('worker ' + worker.process.pid + ' died');
- });
-
- // calculate stats for all workers
- function ModuleMasterCalculateStats() {
-
- for(s in activityGlobal) {
- for(w in activityGlobal[s]) {
- if(!stats.a[s])
- stats.a[s] = PusherAPIStatsRecordCreate();
- stats.a[s].s.m += activityGlobal[s][w].s.m;
- stats.a[s].s.pm += activityGlobal[s][w].s.pm;
- stats.a[s].s.dbrps += activityGlobal[s][w].s.dbrps;
- stats.a[s].s.dbwps += activityGlobal[s][w].s.dbwps;
- stats.a[s].s.dbups += activityGlobal[s][w].s.dbups;
- stats.a[s].n.as += activityGlobal[s][w].n.as;
- if(activityGlobal[s][w].c.s)
- stats.a[s].c.s = true;
- }
- }
-
- Object.keys(cluster.workers).forEach(function(id) {
-
- // send data to worker who send message to master
- cluster.workers[id].send({event:'global-stats', data:stats});
- // ask for current stats from worker
- cluster.workers[id].send({event:'send-activity'});
- });
-
- // reset status before ask workers for new one
- stats.a = new Object();
- stats.n = new Object();
- activityGlobal = new Array();
-
- // set new timeout
- _tseconds = setTimeout(function(){ModuleMasterCalculateStats(); },1000);
- }
- // start stats for all workers
- var _tseconds = setTimeout(function(){ModuleMasterCalculateStats(); },1000);
-
- } else {
-
- /**
- * THIS CODE IS LAUNCH ONLY FOR SLAVE/WORKERS
- *
- * Private Server / Cluster Keys for management
- */
- var NPUSH_SERVER_STATUS_APIKEY = '12345678987654321';
- var NPUSH_SERVER_CONTROL_APIKEY = '12345678987654321';
- /**
- * Module dependencies.
- */
- var express = require('express')
- , stylus = require('stylus')
- , nib = require('nib')
- , sio = require('socket.io')
- , RedisStore = sio.RedisStore
- , databaseUrl = "ni_sockets" //'username:password@example.com:port/mydb'
- , collections = ["messages_public", "messages_private", "accounts"]
- , db = require("mongojs").connect(databaseUrl, collections)
- , port = process.argv[2]
- , qs = require('querystring')
- , util = require('util')
- , os = require('os-utils');
-
- /*******************************************************************************
- * App.
- ******************************************************************************/
- var app = express.createServer();
- /*******************************************************************************
- * App configuration.
- ******************************************************************************/
- app.configure(function () {
- app.use(stylus.middleware({
- src: __dirname + '/public',
- compile: compile
- }));
- app.use(express.static(__dirname + '/public'));
- app.set('views', __dirname);
- app.set('view engine', 'jade');
- function compile (str, path) {
- return stylus(str)
- .set('filename', path)
- .use(nib());
- };
- });
- /*******************************************************************************
- * App routes.
- *
- * This function redirect standard http traffic to index.html file
- ******************************************************************************/
- app.get('/', function (req, res) {
- res.render('public/index', {
- layout: false
- });
- });
- /**
- * App listen.
- */
- app.listen(port, function () {
- var addr = app.address();
- console.log(' app listening on http://' + addr.address + ':' + addr.port);
- });
- /*******************************************************************************
- * Socket.IO server (single process only)
- ******************************************************************************/
- var io = sio.listen(app)
- , nicknames = new Object()
- , activity = new Object()
- , activityGlobal = new Object()
- , nicknameGlobal = new Object()
- , system = os.platform()
- , workersInstances = new Array()
- , allowGlobalTraffic = true;
- // detect system
- if(system == 'linux') {
- system = 1;
- } else {
- system = 2;
- }
- // list of active users
- nicknames = {};
- // activity
- activity = {};
- // socket.io SETTINGS
- io.configure('production', function(){
- io.enable('browser client minification'); // send minified client
- io.enable('browser client etag'); // apply etag caching logic based on version number
- io.enable('browser client gzip'); // gzip the file
- io.set('log level', 1); // reduce logging
- io.set('transports', [ // enable all transports (optional if you want flashsocket)
- 'websocket'
- , 'flashsocket'
- , 'htmlfile'
- , 'xhr-polling'
- , 'jsonp-polling'
- ]);
-
- io.set('store', new RedisStore());
- });
- io.configure('development', function(){
-
- io.set('transports', ['websocket']);
- io.set('store', new RedisStore({ id: function () { cluster.worker.id } }));
- });
- /**
- * Socket.io configuration for:
- *
- * Authorisation process. This function looking for API key and on success
- * return object with proper socket data.
- *
- * @param object handshakeData
- * @param function callback
- */
- io.configure(function (){
- io.set('authorization', function (handshakeData, callback) {
-
- if(!allowGlobalTraffic && NPUSH_SERVER_STATUS_APIKEY != handshakeData.query.k
- && NPUSH_SERVER_CONTROL_APIKEY != handshakeData.query.k)
- return;
- db.collection('apis')
- .find(JSON.parse('{ "key" : "' + handshakeData.query.k + '", "enabled" : "true" }'))
- .forEach(function(err, doc) {
- if(!doc && NPUSH_SERVER_STATUS_APIKEY != handshakeData.query.k
- && NPUSH_SERVER_CONTROL_APIKEY != handshakeData.query.k) {
- callback(null, false);
- } else {
- handshakeData.client_room = handshakeData.query.k;
- handshakeData.client_wellcome_msg = 'Welcome in Newicon Pusher System V.2';
- handshakeData.address = handshakeData.address.address;
- handshakeData.port = handshakeData.address.port;
- handshakeData.api_key = handshakeData.query.k;
- handshakeData.channel = handshakeData.query.c;
- handshakeData.user_id = handshakeData.query.id;
- handshakeData.user_name = handshakeData.query.name;
- handshakeData.user_room = handshakeData.query.room;
-
- PusherAPIsActivity({client:handshakeData.query.k}, 0, 1, 0, 0);
- callback(null, true);
- }
- });
- });
- });
- /**
- * Create proper msg object using data sent form socket to server.
- * Return object ready to send from server to sockets.
- *
- * @param object msg - message from socket which must be broadcasted
- * @param object socket - message sender socket owner
- *
- * @return object
- */
- function PusherMakeMessage(msg, socket) {
- var _time = new Date();
- return {
- dataSystem : {
- user_id: socket.user_id,
- user_name: socket.nickname,
- room: socket.room,
- channel: socket.channel,
- time: parseInt(_time.getTime()),
- client: socket.client,
- type: 0, // 0 - room, 1 - channel, 2 - app
- archive: 0, // 0 - from socket, 1 - from db
- msg_id: 0, // msg id in db
- source: 0 // 0 - socket, 1 - POST
- },
- dataUser : msg.data,
- event : msg.event
- };
- }
- /**
- * Create proper msg object using mongo document. Return object
- * ready to send from server to socket.
- *
- * @param object doc - mongoDB document
- * @return object
- */
- function PusherMakeMessageFromDB(doc) {
- return {
- dataSystem : {
- user_id: doc['user']['id'],
- user_name: doc['user']['name'],
- room: doc['room'],
- channel: '',
- time: doc['date'],
- client: doc['api_key'],
- type: 0, // 0 - room, 1 - channel, 2 - app
- archive: 1, // 0 - from socket, 1 - from db
- msg_id: 0, // msg id in db
- source: doc['source'] // 0 - socket, 1 - POST
- },
- dataUser : doc['message']['data'],
- event : doc['message']['event']
- };
- }
- /**
- * Create proper msg object using POST Request. Return object
- * ready to send from server to socket.
- *
- * @param object request - POST data
- * @return object
- */
- function PusherMakeMessageFromPOST(req) {
- // must be implemented
- }
- /**
- * Updating users activity. Activity is updated for userId
- *
- * @param object socket
- */
- function PusherUserActivityUpdate(socket) {
- var _time = new Date();
- var _search_json = '{ "user_id" : "' + socket.user_id + '" }';
- var _update_json = '{ "user_id" : "' + socket.user_id + '", "date" : ' + _time.getTime() + ', "date_string" : "' + _time.getTime() + '", \n\
- "rooms./' + socket.client + '/' + socket.channel + '/' + socket.room + '/.date" : "' + _time.getTime() + '",\n\
- "rooms./' + socket.client + '/' + socket.channel + '/' + socket.room + '/.name" : "/' + socket.client + '/' + socket.channel + '/' + socket.room + '/" } ';
- _search = JSON.parse(_search_json);
- _update = JSON.parse(_update_json);
- db.collection('act_' + socket.client).update(_search, {
- $set : _update
- } , {
- upsert : true
- } );
- PusherAPIsActivity(socket, 0, 0, 0, 1);
- }
- /**
- * Saving message sent by socket to server
- *
- * @param object msg
- * @param string path
- * @param object socket
- * @param function callback
- */
- function PusherUserMessageSave(msg, path, socket, callback)
- {
- var _data = PusherMakeMessage(msg, socket);
- db.collection('msg_' + socket.handshake.client_room).save({
- "api_key": socket.api_key,
- "room" : path,
- "user" : {
- "id" : socket.user_id,
- "name" : socket.user_name
- } ,
- "date": _data.dataSystem.time,
- "date_string": _data.dataSystem.time.toString(),
- "message" : msg,
- "client" : socket.handshake.address + ':' + socket.handshake.port,
- "source" : 0, // 0 - socket, 1 - POST
- "as" : 0 // active sockets
- }, function(err, obj){
- _data.dataSystem.msg_id = obj._id;
- callback.call(this, _data);
- // send activity stats for debuging
- PusherAPIsActivity(socket, 1, 0, 1, 0);
- });
- }
- /**
- * Saving message sent by POST to server
- *
- * @param object msg
- * @param string path
- * @param object socket
- * @param function callback
- */
- function PusherUserMessageSaveFromPOST(msg, path, req, callback)
- {
- // waiting for implementing. Needed for POST API.
- }
- /**
- * Updating Receipt Collection for each delivery confirmation
- *
- * @param object msg - object with message id as string
- * @param object socket
- */
- function PusherUserMessageRead(msg, socket)
- {
- var _time = new Date();
- var _search_json = '{ "msg_id" : "' + msg.msg_id + '" }';
- var _update_json = '{ "msg_id" : "' + msg.msg_id + '", "users.' + socket.user_id + '.date" : "' + _time.getTime() + '"} ';
- _search = JSON.parse(_search_json);
- _update = JSON.parse(_update_json);
- db.collection('msg_read_' + socket.client).update(_search, {
- $set : _update
- } , {
- upsert : true
- } );
- PusherAPIsActivity(socket, 1, 0, 0, 1);
- }
- /**
- * Increase counters of traffic in sockets and database
- *
- * @param int m - number of messages send in socket
- * @param int dbr - number of db read operations
- * @param int dbw - nubmer of db write operations
- * @param int dbu - number of db update operations
- */
- function PusherAPIsActivity(socket, m, dbr, dbw, dbu) {
- if(!activity[socket.client])
- activity[socket.client] = PusherAPIStatsRecordCreate(socket);
- // sockets sounters
- activity[socket.client].s.m += m;
- // db counters
- activity[socket.client].s.dbrps += dbr;
- activity[socket.client].s.dbwps += dbw;
- activity[socket.client].s.dbups += dbu;
- }
- /**
- * Send API Activity for socket on path npush/stats and npush/users
- * with subscribed event for "apiactivity"
- */
- function PusherAPIsActivityStats()
- {
- // finish here if worker is not first one
- if(cluster.worker.id > 1) return;
-
- // get list of rooms in socket.io
- var rooms = io.sockets.manager.rooms;
- // calculate statistics
- var pmem = process.memoryUsage();
- var ppid = process.pid;
- // temp object with important info for Administrator
- var sstats = {}; // all sockets stats
- var tstats = {mps:0,mpm:0,mph:0,dbr:0,dbw:0,dbu:0}; // global traffic stats
- var aconn = 0; // all active connections
- for(socket in activityGlobal) {
-
- if(!activity[socket])
- activity[socket] = PusherAPIStatsRecordCreate();
- // calculate how many messages was sent last second
- activityGlobal[socket].s.mps = activityGlobal[socket].s.m - activityGlobal[socket].s.pm;
- // calculate how many messages in last minute
- activity[socket].t.mh[activity[socket].t.mhp] = activityGlobal[socket].s.mps;
- if(activity[socket].t.mhp < 60) {
- activity[socket].t.mhp++
- } else {
- activity[socket].t.mhp = 0;
- activity[socket].t.hhp++
- }
- var mpm = 0;
- for(i=0; i<activity[socket].t.mh.length;i++)
- mpm += activity[socket].t.mh[i];
- activity[socket].s.mpm = mpm;
- // calculate how many messages in last hour
- activity[socket].t.hh[activity[socket].t.hhp] = activity[socket].s.mpm;
- if(activity[socket].t.hhp > 59) {
- activity[socket].t.hhp = 0;
- }
- var mph = 0;
- for(i=0; i<activity[socket].t.hh.length;i++)
- mph += activity[socket].t.hh[i];
- activity[socket].s.mph = mph;
- // send stats to subscribed sockets
- var _path = '/' + socket + '/npush/api/stats';
- activity[socket].s.proc = {mem:pmem.rss, pid:ppid};
- activityGlobal[socket].s.mpm = activity[socket].s.mpm;
- activityGlobal[socket].s.mph = activity[socket].s.mph;
- for(room in rooms)
- if(room.search(_path) == 0) {
- io.sockets.in(room.substr(1, room.length)).emit('apiactivity', activityGlobal[socket].s);
- PusherAPIsActivity({client:socket}, 1, 0, 0, 0);
- }
- // send users sockets list to subscribed sockets
- if(activityGlobal[socket].c.s) {
- var _path = '/' + socket + '/npush/api/users';
- for(room in rooms)
- if(room.search(_path) == 0) {
- io.sockets.in(room.substr(1, room.length)).emit('usersapp', nicknameGlobal[socket]);
- PusherAPIsActivity({client:socket}, 1, 0, 0, 0);
- }
- activityGlobal[socket].c.s = false;
- }
- // set admin-stats for this API
- sstats[socket] = {mps:activityGlobal[socket].s.mps,mpm:activityGlobal[socket].s.mpm,s:activityGlobal[socket].n.as,n:activityGlobal[socket].n.n};
- // gobal traffic info
- tstats.mps += activityGlobal[socket].s.mps;
- tstats.mpm += activityGlobal[socket].s.mpm;
- tstats.mph += activityGlobal[socket].s.mph;
- tstats.dbr += activityGlobal[socket].s.dbrps;
- tstats.dbw += activityGlobal[socket].s.dbwps;
- tstats.dbu += activityGlobal[socket].s.dbups;
-
- // summ all connections
- aconn += activityGlobal[socket].n.as;
- }
- // prepare system stats
- os.cpuUsage(function(v) {
- var _path = '/' + NPUSH_SERVER_STATUS_APIKEY + '/npush/system/stats';
- var _data = {
- node: new Array(),
- api:{
- act:sstats
- },
- os:{
- cpu:v,
- tm:os.totalmem(),
- fm:os.freemem(),
- node:{
- i: workersInstances.length,
- s: startInstancesNumber
- }
- },
- mongo: new Array(),
- sockets:{
- active:aconn,
- traffic:allowGlobalTraffic
- },
- traffic:tstats
- };
- if(system == 1) { // linux
- var command = 'top -n1 -b';
- require('child_process').exec(command, function(error, stdout, stderr) {
- var lines = stdout.split("\n");
- lines.forEach(function(_item,_i) {
- var _p = _item.replace( /[\s\n\r]+/g,' ').split(' ');
- if(String(_p[12]).indexOf('mongod') > -1) {
- _data.mongo.push({pid:_p[1],cpu:_p[9],pmem:_p[6],rss:_p[10],name:_p[12]});
- } else if(_p[0] == ppid) {
- _data.node = {pid:_p[0],cpu:_p[8],pmem:_p[5],rss:_p[9],name:_p[11]};
- }
- });
- for(room in rooms)
- if(room.search(_path) == 0) {
- io.sockets.in(room.substr(1, room.length)).emit('announcement', _data);
- PusherAPIsActivity({client:NPUSH_SERVER_STATUS_APIKEY}, 1, 0, 0, 0);
- }
- });
- } else { // mac
- var command = 'ps -eo pid,pcpu,pmem,rss,comm';
- //var command = 'top';
-
- require('child_process').exec(command, function(error, stdout, stderr) {
- var lines = stdout.split("\n");
- lines.forEach(function(_item,_i) {
- var _p = _item.replace( /[\s+\n\r]+/g,' ').split(' ');
-
- if(String(_p[4]).indexOf('mongod') > -1) {
- _data.mongo.push({pid:_p[0],cpu:_p[1],pmem:_p[2],rss:_p[3],name:_p[4]});
- } else if((parseInt(_p[0]) > 0 && PusherFindInArray(workersInstances ,_p[0]))
- || (_p[0] == '' && parseInt(_p[1]) > 0 && PusherFindInArray(workersInstances ,_p[1]))) {
-
- // we need get list of PIDs in cluster and get data for them
- if(_p[0] == '') {
- _data.node.push({pid:_p[1],cpu:_p[2],pmem:_p[3],rss:_p[4],name:_p[5]});
- } else {
- _data.node.push({pid:_p[0],cpu:_p[1],pmem:_p[2],rss:_p[3],name:_p[4]});
- }
- }
- });
- for(room in rooms)
- if(room.search(_path) == 0) {
- io.sockets.in(room.substr(1, room.length)).emit('announcement', _data);
- PusherAPIsActivity({client:NPUSH_SERVER_STATUS_APIKEY}, 1, 0, 0, 0);
- }
- });
- }
- });
- }
-
- /**
- * Listen for Master Cluster Messages
- * Send Respond for Master requests.
- *
- * @param object data as message from master process
- */
-
- function ModulePusherMessageHandler(data) {
-
- if(data.event == 'global-stats') {
-
- // all worker PIDs
- workersInstances = data.data.w;
- // data activity from master (all workers)
- activityGlobal = data.data.a;
- // data nicknames from master (all workers)
- nicknameGlobal = data.data.n;
- // global traffic
- allowGlobalTraffic = data.data.t.a;
- // send stats to scokets
- PusherAPIsActivityStats();
-
- } else if(data.event == 'send-activity') {
-
- // data actovity from worker to master
- process.send({
- event:'worker-activity', data:{a:activity,n:nicknames} });
-
- // some action after send stats to master
- for(socket in activity) {
-
- activity[socket].s.pm = activity[socket].s.m;
- activity[socket].s.dbrps = activity[socket].s.dbwps = activity[socket].s.dbups = 0;
- }
- }
- }
-
- /**
- * Cluster messages handler
- */
-
- process.on('message', ModulePusherMessageHandler);
- /*
- * Handle event for each socket. This 'connection' event function is called
- * when handshake proccess return true.
- *
- * Events:
- * - message
- * - message-read
- * - message boradcast rooms
- * - message broadcast app
- * - message broadcast channel
- * - public_activity_ms
- * - public_activity
- * - disconnect
- *
- */
- io.sockets.on('connection', function (socket) {
- // USER LOGIN AND ENVIRONMENT SETTINGS
- // SEND WELCOME MESSAGE
- io.sockets.socket(socket.id).emit('announcement', socket.handshake.client_wellcome_msg);
- // SET USER SOCKET PARAMETERS
- socket.api_key = socket.handshake.api_key; // THIS IS API KEY
- socket.channel = socket.handshake.channel;
- socket.client = socket.handshake.client_room; // THIS IS API KEY
- socket.path = socket.handshake.client_room + '/' + socket.handshake.channel + '/' + socket.handshake.user_room;
- socket.room = socket.handshake.user_room;
- socket.user_id = socket.handshake.user_id;
- socket.user_name = socket.handshake.user_name;
-
- // ADD USER TO LIST
-
- if(!nicknames[socket.handshake.client_room])
- nicknames[socket.handshake.client_room] = {};
-
- if(!activity[socket.handshake.client_room])
- activity[socket.handshake.client_room] = PusherAPIStatsRecordCreate(socket);
- if(!nicknames[socket.handshake.client_room][socket.handshake.channel])
- nicknames[socket.handshake.client_room][socket.handshake.channel] = {};
- if(!nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room])
- nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room] = {};
-
- nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room][socket.handshake.user_id] = socket.nickname = socket.handshake.user_name;
-
- // JOIN ROOM
- socket.join(socket.path);
- // set info in activity object - list will be send to watchers
- activity[socket.client].c.s = true;
- // update connected user in socket -> as is active-sockets
- activity[socket.client].n.as++;
- // USER MESSAGES
- socket.on('message', function (_msg) {
- if(!allowGlobalTraffic) return;
- // set path to room
- var _path = '/' + socket.handshake.client_room + '/' + socket.channel + '/' + socket.room + '/';
- // save message in db
- PusherUserMessageSave(_msg, _path, socket, function(_data) {
- // send message
- io.sockets.in(socket.path).emit('message', _data);
- });
- });
- socket.on('message-read', function (_msg) {
- PusherUserActivityUpdate(socket);
- PusherUserMessageRead(_msg, socket);
- });
- // BROADCAST MESSAGES
- socket.on('message boradcast rooms', function (_msg, fn) {
-
- if(!allowGlobalTraffic) return;
- var _path = '/' + socket.client + '/' + socket.channel + '/';
- // save message in db
- PusherUserMessageSave(_msg, _path, socket, function(_data) {
- // broadcast to app
- _data.dataSystem.type = 1;
- // get all rooms
- var rooms = io.sockets.manager.rooms;
- for(room in rooms)
- if(room.search(_path) == 0)
- io.sockets.in(room.substr(1, room.length)).emit('message', _data);
- });
- });
- socket.on('message broadcast app', function (_msg, fn) {
-
- if(!allowGlobalTraffic) return;
- var _path = '/' + socket.client + '/';
- // save message in db
- PusherUserMessageSave(_msg, _path, socket, function(_data) {
- // broadcast to app
- _data.dataSystem.type = 3;
- // get all rooms
- var rooms = io.sockets.manager.rooms;
- for(room in rooms)
- if(room.search(_path) == 0)
- io.sockets.in(room.substr(1, room.length)).emit('message', _data);
- });
- });
- socket.on('message broadcast channel', function (_channel, _msg, fn) {
-
- if(!allowGlobalTraffic) return;
- var _path = '/' + socket.client + '/' + _channel + '/';
- // save message in db
- PusherUserMessageSave(_msg, _path, socket, function(_data) {
- // broadcast to app
- _data.dataSystem.type = 2;
- // get all rooms
- var rooms = io.sockets.manager.rooms;
- for(room in rooms)
- if(room.search(_path) == 0)
- io.sockets.in(room.substr(1, room.length)).emit('message', _data);
- });
- });
- // READ TIME OF LAST ACTIVITY IN MS FROM DB AND SEND UNREAD MESSAGES
- socket.on('public_activity_ms', function (_period, fn) {
-
- if(!allowGlobalTraffic) return;
- var _search_time = ' { "user_id" : "' + socket.user_id + '", "rooms./' + socket.client + '/' + socket.channel + '/' + socket.room + '/.date" : {"$exists": true} } ';
- _search = JSON.parse(_search_time);
- db.collection('act_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- var _time = new Date();
- socket.user_last_activity = _time.getTime() - _period;
- var _time_info = new Date(socket.user_last_activity*1);
- // send info
- io.sockets.socket(socket.id).emit('announcement', 'activity since ' + _time_info + ' on '
- + '/' + socket.client + '/' + socket.channel + '/' + socket.room + '/');
- // SEARCH FOR MESSAGES FOR ROOM
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/' + socket.channel + '/' + socket.room + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- // SEARCH FOR MESSAGES FOR CHANNEL
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/' + socket.channel + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- // SEARCH FOR MESSAGES FOR APP
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- });
- });
- // READ TIME OF LAST ACTIVITY FROM DB AND SEND UNREAD MESSAGES
- socket.on('public_activity', function (nick, fn) {
-
- if(!allowGlobalTraffic) return;
- var _search_time = ' { "user_id" : "' + socket.user_id + '", "rooms./' + socket.client + '/' + socket.channel + '/' + socket.room + '/.date" : {"$exists": true} } ';
- search = JSON.parse(_search_time);
- db.collection('act_' + socket.handshake.client_room).find(search).forEach(function(err, doc) {
- if (!doc) return;
- socket.user_last_activity = doc['rooms']['/' + socket.client + '/' + socket.channel + '/' + socket.room + '/']['date'];
- var _time = new Date(socket.user_last_activity*1);
- // send info
- io.sockets.socket(socket.id).emit('announcement', 'last activity ' + _time + ' on '
- + '/' + socket.client + '/' + socket.channel + '/' + socket.room + '/');
- // SEARCH FOR MESSAGES FOR ROOM
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/' + socket.channel + '/' + socket.room + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- // SEARCH FOR MESSAGES FOR CHANNEL
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/' + socket.channel + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- // SEARCH FOR MESSAGES FOR APP
- var _search_msg = ' { "date" : { "$gt" : ' + socket.user_last_activity + ' }, "room" : "/' + socket.client + '/" } ';
- _search = JSON.parse(_search_msg);
- db.collection('msg_' + socket.handshake.client_room).find(_search).forEach(function(err, doc) {
- if (!doc) return;
- io.sockets.socket(socket.id).emit('message', PusherMakeMessageFromDB(doc));
- PusherAPIsActivity(socket, 1, 1, 0, 0);
- });
- });
- });
-
- // SERVER CONTROLL COMMAND
-
- socket.on('system', function(data) {
-
- process.send({
- event:'system-command', data:data });
- });
- // DISCONNECT AND SAVE TIME OF EVENT IN DB
- socket.on('disconnect', function () {
- // delete user from list
- if (!socket.nickname) return;
- PusherUserActivityUpdate(socket);
- try {
- // delete user_id from array
- delete nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room][socket.user_id];
- // delete room space in array if no user exist
- if(Object.keys(nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room]).length == 0)
- delete nicknames[socket.handshake.client_room][socket.handshake.channel][socket.handshake.user_room];
- // delete channe; space in array if no channel exist
- if(Object.keys(nicknames[socket.handshake.client_room][socket.handshake.channel]).length == 0)
- delete nicknames[socket.handshake.client_room][socket.handshake.channel];
- // delete channe; space in array if no channel exist
- if(Object.keys(nicknames[socket.handshake.client_room]).length == 0) {
- delete nicknames[socket.handshake.client_room];
- delete activity[socket.handshake.client_room];
- } else {
- // set info in activity object - list will be send to watchers
- activity[socket.client].c.s = true;
- // update connected user in socket -> as is active-sockets
- activity[socket.client].n.as--;
- }
- } catch(e) {
- console.log('error on disconnect');
- }
- });
- });
- /*******************************************************************************
- * POST REQUESTS
- * NEED OPTIMIZATION IN VERSION 3.0
- *
- * TO DO !!!!!!!!!!!!!!!!!!!!!!
- * - implement universal function to save/update for POST API INTERFACE
- *
- ******************************************************************************/
- function PusherSendMessageFromPOST(req, res, type, callback) {
- if(req.method == 'POST' && allowGlobalTraffic) {
- // DATA
- var body ='';
- var _time_server = new Date();
- req.on('data', function (data) {
- body += data;
- });
- req.on('end', function () {
- var _search_msg = ' { "key" : "' + req.params.k + '", "enabled" : "true" } ';
- _search = JSON.parse(_search_msg);
- db.collection('apis').find(_search).forEach(function(err, doc) {
- if (!doc) {
- res.send('AUTH_ERROR');
- return;
- } else {
- var _path = '';
- try {
- _msg_object = JSON.parse(body);
- }
- catch (e) {
- _msg_object = { data : body, event : 'message' }
- }
- try {
- _time_stamp = parseInt(req.params.t);
- } catch(e) {
- _time_stamp = req.params.t;
- }
- var data = {
- dataSystem : {
- client:req.params.k,
- user_id:req.params.suid,
- user_name:req.params.sun,
- room:req.params.r, // becarefull here
- channel:req.params.c, // becarefull here
- time:Math.floor(_time_server.getTime()),
- type:type, // becarefull here
- archive:0,
- msg_id:0,
- source:1
- },
- dataUser : _msg_object.data,
- event : _msg_object.event
- }
- if(req.params.k != '')
- _path = req.params.k;
- if(req.params.c != '')
- _path = _path + '/' + req.params.c; // becarefull here
- if(req.params.r != '')
- _path = _path + '/' + req.params.r // becarefull here
- db.collection('msg_'+req.params.k).save({
- "api_key": req.params.k,
- "room" : '/' + _path + '/',
- "user" : {
- "id" : req.params.suid,
- "name" : req.params.sun
- } ,
- "date": _time_server.getTime(),
- "date_string": _time_server.getTime().toString(),
- "message" : _msg_object,
- "client" : req.connection.remoteAddress + ':' + req.connection.remotePort,
- "source" : 1,
- "as" : 0
- }, function(err, obj) {
- data.dataSystem.msg_id = obj._id;
- PusherUserActivityUpdate(data.dataSystem);
- // update API Activity Stats
- PusherAPIsActivity({client:req.params.k}, 1, 1, 1, 0);
- callback.call(this, true, data, _path);
- });
- }
- });
- });
- } else {
-
- res.send('TRAFFIC_STOPPED');
- return;
- }
- }
- // POST FOR APP/CHANNEL/ROOM
- app.post('/api/channel/:c/room/:r/api/:k/version/:v/t/:t/suid/:suid/suname/:sun', function (req, res) {
- // :c - destination channel
- // :r - destination room
- // :k - api key
- // :v - api version
- // :t - time stamp
- // :suid - sender user id
- // :sun - sender user name
- // EXAMPLE:
- // curl -H "Content-Type: application/post"
- // -d '{"object":"helloworld"}'
- // "http://push.vm.newicon.net:80/api/channel/ch1/room/room1/api/ab3245cbeaf456244abcdfa/version/2.0/t/234324/suid/1/suname/me"
- PusherSendMessageFromPOST(req, res, 0, function(status, data, path) {
- if(status) {
- if(req.params.r != '') {
- io.sockets.in(path).emit('message', data);
- }
- res.send('OK');
- } else {
- res.send('AUTH_ERROR');
- }
- });
- });
- // POST FOR /APP/CHANNEL
- app.post('/api/channel/:c/api/:k/version/:v/t/:t/suid/:suid/suname/:sun', function (req, res) {
- // :c - destination channel
- // :r - destination room
- // :k - api key
- // :v - api version
- // :t - time stamp
- // :suid - sender user id
- // :sun - sender user name
- // EXAMPLE:
- // curl -H "Content-Type: application/post"
- // -d '{"object":"helloworld"}'
- // "http://push.vm.newicon.net:80/api/channel/ch1/api/ab3245cbeaf456244abcdfa/version/2.0/t/234324/suid/1/suname/me"
- if(req.method == 'POST' && allowGlobalTraffic) {
- // DATA
- var body ='';
- var _time_server = new Date();
- req.on('data', function (data) {
- body += data;
- });
- req.on('end', function () {
- var _search_msg = ' { "key" : "' + req.params.k + '", "enabled" : "true" } ';
- _search = JSON.parse(_search_msg);
- db.collection('apis').find(_search).forEach(function(err, doc) {
- if (!doc) {
- res.send('AUTH_ERROR');
- return;
- } else {
- var rooms = io.sockets.manager.rooms;
- var _path = '';
- try {
- _msg_object = JSON.parse(body);
- }
- catch (e) {
- _msg_object = { data : body, event : 'message' }
- }
- try {
- _time_stamp = parseInt(req.params.t);
- } catch(e) {
- _time_stamp = req.params.t;
- }
- var data = {
- dataSystem : {
- client:req.params.k,
- user_id:req.params.suid,
- user_name:req.params.sun,
- room:'',
- channel:req.params.c,
- time:Math.floor(_time_server.getTime()),
- type:2,
- archive:0,
- msg_id:0,
- source:1
- },
- dataUser : _msg_object.data,
- event : _msg_object.event
- }
- if(req.params.k != '')
- _path = req.params.k;
- if(req.params.c != '')
- _path = _path + '/' + req.params.c;
- db.collection('msg_'+req.params.k).save({
- "api_key": req.params.k,
- "room" : '/' + _path + '/',
- "user" : {
- "id" : req.params.suid,
- "name" : req.params.sun
- } ,
- "date": _time_server.getTime(),
- "date_string": _time_server.getTime().toString(),
- "message" : _msg_object,
- "client" : req.connection.remoteAddress + ':' + req.connection.remotePort,
- "source" : 1,
- "as" : 0
- }, function(err, obj) {
- data.dataSystem.msg_id = obj._id;
- PusherUserActivityUpdate(data.dataSystem);
- if(req.params.c != '') {
- for(room in rooms) {
- if(room.search('/'+_path + '/') == 0) {
- io.sockets.in(room.substr(1, room.length)).emit('message', data);
- }
- }
- }
- // update API Activity Stats
- PusherAPIsActivity({client:req.params.k}, 1, 1, 1, 0);
- res.send('OK');
- return;
- });
- }
- });
- });
- } else {
-
- res.send('TRAFFIC_STOPPED');
- return;
- }
- });
- // POST FOR APP/
- app.post('/api/api/:k/version/:v/t/:t/suid/:suid/suname/:sun', function (req, res) {
- // :c - destination channel
- // :r - destination room
- // :k - api key
- // :v - api version
- // :t - time stamp
- // :suid - sender user id
- // :sun - sender user name
- // EXAMPLE:
- // curl -H "Content-Type: application/post"
- // -d '{"object":"helloworld"}'
- // "http://push.vm.newicon.net:80/api/api/ab3245cbeaf456244abcdfa/version/2.0/t/234324/suid/1/suname/me"
- if(req.method == 'POST' && allowGlobalTraffic) {
- // DATA
- var body ='';
- var _time_server = new Date();
- req.on('data', function (data) {
- body += data;
- });
- req.on('end', function () {
- var _search_msg = ' { "key" : "' + req.params.k + '", "enabled" : "true" } ';
- _search = JSON.parse(_search_msg);
- db.collection('apis').find(_search).forEach(function(err, doc) {
- if (!doc) {
- res.send('AUTH_ERROR');
- return;
- } else {
- var rooms = io.sockets.manager.rooms;
- var _path = '';
- try {
- _msg_object = JSON.parse(body);
- }
- catch (e) {
- _msg_object = { data : body, event : 'message' }
- }
- try {
- _time_stamp = parseInt(req.params.t);
- } catch(e) {
- _time_stamp = req.params.t;
- }
- var data = {
- dataSystem : {
- client:req.params.k,
- user_id:req.params.suid,
- user_name:req.params.sun,
- room:'',
- channel:'',
- time:Math.floor(_time_server.getTime()),
- type:3,
- archive:0,
- msg_id:0,
- source:1
- },
- dataUser : _msg_object.data,
- event : _msg_object.event
- }
- if(req.params.k != '')
- _path = req.params.k;
- db.collection('msg_'+req.params.k).save({
- "api_key": req.params.k,
- "room" : '/' + _path + '/',
- "user" : {
- "id" : req.params.suid,
- "name" : req.params.sun
- } ,
- "date": _time_server.getTime(),
- "date_string": _time_server.getTime().toString(),
- "message" : _msg_object,
- "client" : req.connection.remoteAddress + ':' + req.connection.remotePort,
- "source" : 1,
- "as" : 0
- }, function(err, obj) {
- data.dataSystem.msg_id = obj._id;
- PusherUserActivityUpdate(data.dataSystem);
- if(req.params.k != '') {
- for(room in rooms) {
- if(room.search('/'+_path + '/') == 0)
- {
- io.sockets.in(room.substr(1, room.length)).emit('message', data);
- }
- }
- }
- // update API Activity Stats
- PusherAPIsActivity({client:req.params.k}, 1, 1, 1, 0);
- res.send('OK');
- return;
- });
- }
- });
- });
- } else {
-
- res.send('TRAFFIC_STOPPED');
- return;
- }
- });
- /**
- * Helpers function
- *
- * PusherRoundNumber
- * @param float num
- * @param int decimal
- */
- function PusherRoundNumber(num, dec) {
- return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
- }
-
- function PusherFindInArray(_array, _value) {
-
- for(_x in _array) {
-
- if(_value == _array[_x]) return true;
- }
-
- return false;
- }
-
- // end of cluster worker
- }