hersserver
Version:
A 'now'-alike server extension not using socket.io
241 lines (240 loc) • 6.05 kB
JavaScript
var responding = require('./responding');
var url=require('url');
var EventEmitter = require('events').EventEmitter;
var randomBytes = require('crypto').randomBytes;
function logMyNow(){
return;
console.log.apply(console,arguments);
};
function cookieObject(cs){
var ret = {};
var rhc = cs.split(';');
if(rhc){
var rhcl=rhc.length;
for(var i=0; i<rhcl; i++){
var rhce = rhc[i];
while(rhce[0]===' '){rhce=rhce.substring(1);}
var ie = rhce.indexOf('=');
if(ie){
ret[rhce.substring(0,ie)]=rhce.substring(ie+1);
}
}
}
return ret;
};
function NowClient(id,cookie,response,diedcb){
this.clientId=id;
var t = this;
var oa = response.connection.remoteAddress;
var op = response.connection.remotePort;
this.diedcb = function(){logMyNow(id,oa,':',op,'is dead now');diedcb(t);};
this.remoteAddress = oa;
this.remotePort = op;
this.cookie=cookie;
this.invoke('setClientId',{clientId:this.clientId},response);
}
NowClient.prototype.setTimeout = function(){
this.clearTimeout();
logMyNow(this.clientId,'setting Timeout');
this.timeout = setTimeout(this.diedcb,15000);
};
NowClient.prototype.clearTimeout = function(){
if(this.timeout){
logMyNow(this.clientId,'clearing Timeout');
clearTimeout(this.timeout);
delete this.timeout;
}
};
NowClient.prototype.invoke = function(method,paramobj,response){
var io = {method:method,paramobj:paramobj};
//logMyNow('should invoke',method,paramobj);
if(this.response){
//logMyNow('invoking',method,'with',io);
this.sendInvoke(this.response,[io]);
delete this.response;
if(response){
response.end();
}
return;
};
if(response){
this.sendInvoke(response,[io]);
return;
}
if(!this.queue){
this.queue=[];
}
this.queue.push(io);
};
NowClient.prototype.hitMe = function(response){
if(this.response){
this.response.end();
delete this.response;
}
this.clearTimeout();
var q = this.queue;
if(!q){
this.response = response;
this.remoteAddress = response.connection.remoteAddress;
this.remotePort = response.connection.remotePort;
var t = this;
//this.response.connection.setTimeout(5000);
setTimeout(function(){t.invoke('',{});},5000);
this.response.on('close',function(){delete t.response; t.setTimeout();});
return;
}
//logMyNow('dumping queue',this.queue);
this.sendInvoke(response);
};
NowClient.prototype.sendInvoke = function(response,arry){
var a = this.queue||[];
a = a.concat(arry||[]);
if(!a.length){return;}
response.write(JSON.stringify({invoke:a}));
response.end();
delete this.queue;
this.setTimeout();
};
function NowGroup(){
this.clients={};
}
NowGroup.prototype.addUser = function(cid){
if(!cid){return;}
this.clients[cid]=1;
};
function Now(){
this.clients = {};
this.groups = {};
this.cnt = 0;
}
Now.prototype = new EventEmitter();
Now.prototype.constructor = Now;
Now.prototype.byteToString = function(b){
var ret='';
ret+=(33+(b&0x0f));
b>>=4;
ret+=(33+(b&0x0f));
return ret;
};
Now.prototype.inc = function(){
this.cnt++;
if(this.cnt>100000000){
this.cnt=1;
}
var rb = randomBytes(2);
var rbs = '';
rbs+=this.byteToString(rb[0]);
rbs+=this.byteToString(rb[1]);
return (new Date()).getTime()+'-'+rbs+'-'+this.cnt;
};
Now.prototype.getGroup = function(name){
var g = this.groups[name];
if(!g){
g=new NowGroup();
this.groups[name]=g;
g.now = new Now();
}
return g;
};
Now.prototype.removeGroup = function(name){
delete this.groups[name];
};
Now.prototype.initialize = function(){
return {now:this};
};
Now.prototype.init = function(cid,request,response){
if(cid){
var m = this.clients[cid];
if(m){
m.hitMe(response);
return;
}
logMyNow(cid,'not found');
}
//logMyNow(response.socket.remoteAddress,cid,'not found');
var c = this.inc();
var t = this;
var ck = {};
if(request.headers.cookie){
ck = cookieObject(request.headers.cookie);
}
var nc = new NowClient(c,ck,response,function(client){
t.emit('disconnect',client);
t.remove(client);
});
this.clients[c] = nc;
this.emit('connect',nc);
};
Now.prototype.deinit = function(cid){
logMyNow('deiniting',cid);
var c = this.clients[cid];
if(!c){return;}
this.emit('disconnect',c);
this.remove(c);
};
Now.prototype.remove = function(client){
var cid = client.clientId;
client.clearTimeout();
for(var n in client){
delete client[n];
}
for(var n in this.groups){
delete this.groups[n].clients[cid];
}
delete this.clients[cid];
};
Now.prototype.broadcast = function(){
var args = Array.prototype.slice.call(arguments);
for(var n in this.clients){
//logMyNow('invoking on',c,args);
var c = this.clients[n];
c.invoke.apply(c,args);
}
};
Now.prototype.broadcastOnGroup = function(groupname){
var args = Array.prototype.slice.call(arguments,1);
var g = this.groups[groupname];
if(!g){
logMyNow('group',groupname,'does not exist');
return;
}
var fordel=[];
logMyNow('group',groupname,'clients',g.clients);
for(var n in g.clients){
logMyNow('invoking on',n,'with',args);
var c = this.clients[n];
if(!c){
//logMyNow('missing client for',n);
fordel.push(n);
}else{
c.invoke.apply(c,args);
}
}
var fdl = fordel.length;
for(var i=0; i<fdl; i++){
delete g.clients[fordel[i]];
}
};
Now.prototype.getClient = function(cid,cb){
if(typeof cb !== 'function'){return;}
var c = this.clients[cid];
cb.apply(c,[c]);//provide the client as 'this' or as the first and only param
};
var _NowInstance = new Now();
responding.createResponder({
name:'now/init',
handler:function(cid){
_NowInstance.init(cid,this.request,this.response);
},
params:['clientId'],
parent:_NowInstance
});
responding.createResponder({
name:'now/deinit',
handler:function(cid){
_NowInstance.deinit(cid,this.request,this.response);
},
params:['clientId'],
parent:_NowInstance
});
module.exports = _NowInstance;