Skip to content

Commit

Permalink
working on presence/private channels
Browse files Browse the repository at this point in the history
  • Loading branch information
daraosn committed May 7, 2012
1 parent 258e3f7 commit c8e8c69
Show file tree
Hide file tree
Showing 11 changed files with 405 additions and 317 deletions.
4 changes: 2 additions & 2 deletions config.template.js
@@ -1,4 +1,4 @@
module.exports={
'password': 'bGINGS/(ADNfg78GASIMDbkASbj' // replace with your own
,'port': 4567 // default: 4567
password: 'bGINGS/(ADNfg78GASIMDbkASbj' // replace with your own
,port: 4567 // default: 4567
}
17 changes: 17 additions & 0 deletions lib/configure.js
@@ -0,0 +1,17 @@
app.configure(function(){
// app.set('views', __dirname + '/views');
// app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(process.cwd() + '/public'));
});

app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
// app.get('/example.html', function(req, res) { res.redirect('/'); }); // Disable /example.html on production
app.use(express.errorHandler());
});
190 changes: 190 additions & 0 deletions lib/mustekala.client.js
@@ -0,0 +1,190 @@
var Mustekala=function(setup) {
setup=setup || {};
var m={
'version': '0.1.0'
,'connected': false
,'socket': {}
,'events': {}
// configurable settings
,'authUrl': setup['authUrl'] || false // auth url for private/presence channels
,'debug': setup['debug'] || 0 // debugging (verbose levels: 0,1,2)
}

m.unbindEvents=function() {
m.events={
'connect': []
,'disconnect': []
,'trigger': []
,'subscribe': []
,'unsubscribe': []
,'log': []
}
}
m.unbindEvents();
m.connect=function(connectListener) {
// connect to socket
m.unbindEvents();
m.onConnect(connectListener);
m.socket=io.connect('/mustekala'); // TODO: this must be provided by the app configuration on setup
m.socket.on('connect', function() {
m.connected = true;
m.triggerEvent('connect');
});
// set event listeners
m.socket.on('log', function(data) {
m.triggerEvent('log', data);
});
m.socket.on('subscribe', function(channel) {
m.triggerEvent('subscribe', channel);
});
m.socket.on('unsubscribe', function(channel) {
m.triggerEvent('unsubscribe', channel);
});
m.socket.on('trigger', function(channel, action, data) {
m.triggerEvent('trigger', channel, action, data);

var key='-'+channel+'-'+action;
if(m.events[key])
m.triggerEvent(key, data);
});
m.socket.on('disconnect', function() {
m.connected = false;
m.triggerEvent('disconnect');
})
}
m.subscribe=function(channel) {
if(m.connected) {
var regexp=new RegExp(/^(presence|private)@(.+)$/);
var regexpChannel=regexp.exec(channel);
if(regexpChannel) {
// Private or Presence channels:
var channelType=regexpChannel[1];
var channel=regexpChannel[2];
if(m.authUrl) {
AjaxRequest.post({
'url': m.authUrl
,'onSuccess': function(response) {
try {
authKey=JSON.parse(response.responseText).authKey;
if(authKey) {
m.socket.emit('subscribe',channel,channelType,authKey);
} else {
throw true
}
} catch(e) {
throw 'Unable to parse your '+channelType+' channel authorization key.';
}
}, 'onError': function() {
throw 'Unable to authentificate your '+channelType+' channel subscription.';
}
})
return new MustekalaChannel(channel);
} else {
throw 'You must provide an authUrl when instancing Mustekala to create private or presence channels.'
}
} else {
// Public channel:
m.socket.emit('subscribe', channel);
return new MustekalaChannel(channel);
}
} else {
throw 'Mustekala is not connected.';
if(m.debug)
console.log('m.subscribe', 'Not connected.');
}
}
// TODO: m.disconnet()
/*
* This is for testing purposes, do not use it in your front-end code on production
*/
m.trigger=function(password, channel, action, data) {
if(m.connected) {
if(m.debug)
console.log('m.trigger', 'WARNING: This is for testing purposes only, do not use on production!');
m.socket.emit('trigger', password, channel, action, data);
} else {
throw 'Mustekala is not connected.';
if(m.debug)
console.log('m.trigger', 'Not connected.');
}
}

// Events
m.onConnect=function(listener) {
m.events['connect'].push(listener);
if(m.debug>1)
console.log('m.onConnect');
}
m.onDisconnect=function(listener) {
m.events['disconnect'].push(listener);
if(m.debug>1)
console.log('m.onDisconnect');
}
m.onLog=function(listener) {
m.events['log'].push(listener);
if(m.debug>1)
console.log('m.onDisconnect');
}
m.onSubscribe=function(listener) {
m.events['subscribe'].push(listener);
if(m.debug>1)
console.log('m.onSubscribe');
}
m.onUnsubscribe=function(listener) {
m.events['unsubscribe'].push(listener);
if(m.debug>1)
console.log('m.onUnsubscribe');
}
m.onTrigger=function(listener) {
m.events['trigger'].push(listener);
if(m.debug>1)
console.log('m.onTrigger');
}
m.triggerEvent=function() {
if(m.debug)
console.log('m.triggerEvent',m.triggerEvent.arguments);

event=m.triggerEvent.arguments[0];
args=[];
for(var i=1; i<m.triggerEvent.arguments.length; i++)
args.push(m.triggerEvent.arguments[i]);
for(var i in m.events[event]) {
try {
m.events[event][i].apply(m,args);
} catch(e) {
if(m.debug)
console.log(e.toString());
}
}
}

// Helpers
function MustekalaChannel(channel) {
return {
name: channel
,on: function(action, handler) {
if(m.debug) {
console.log('m.subscribe().channel<'+channel+'>.on', action);
}
var key='-'+channel+'-'+action;
if(!m.events[key])
m.events[key]=[];
m.events[key].push(function(data) {
handler(data);
});
}
// TODO: add more methods & attributes (status, unsubscribe, etc.)
}
}

return m;
};

/*! AjaxRequest Author: http://www.ajaxtoolbox.com/ */
function AjaxRequest(){var a=new Object();a.timeout=null;a.generateUniqueUrl=true;a.url=window.location.href;a.method="GET";a.async=true;a.username=null;a.password=null;a.parameters=new Object();a.requestIndex=AjaxRequest.numAjaxRequests++;a.responseReceived=false;a.groupName=null;a.queryString="";a.responseText=null;a.responseXML=null;a.status=null;a.statusText=null;a.aborted=false;a.xmlHttpRequest=null;a.onTimeout=null;a.onLoading=null;a.onLoaded=null;a.onInteractive=null;a.onComplete=null;a.onSuccess=null;a.onError=null;a.onGroupBegin=null;a.onGroupEnd=null;a.xmlHttpRequest=AjaxRequest.getXmlHttpRequest();if(a.xmlHttpRequest==null){return null}a.xmlHttpRequest.onreadystatechange=function(){if(a==null||a.xmlHttpRequest==null){return}if(a.xmlHttpRequest.readyState==1){a.onLoadingInternal(a)}if(a.xmlHttpRequest.readyState==2){a.onLoadedInternal(a)}if(a.xmlHttpRequest.readyState==3){a.onInteractiveInternal(a)}if(a.xmlHttpRequest.readyState==4){a.onCompleteInternal(a)}};a.onLoadingInternalHandled=false;a.onLoadedInternalHandled=false;a.onInteractiveInternalHandled=false;a.onCompleteInternalHandled=false;a.onLoadingInternal=function(){if(a.onLoadingInternalHandled){return}AjaxRequest.numActiveAjaxRequests++;if(AjaxRequest.numActiveAjaxRequests==1&&typeof(window.AjaxRequestBegin)=="function"){AjaxRequestBegin()}if(a.groupName!=null){if(typeof(AjaxRequest.numActiveAjaxGroupRequests[a.groupName])=="undefined"){AjaxRequest.numActiveAjaxGroupRequests[a.groupName]=0}AjaxRequest.numActiveAjaxGroupRequests[a.groupName]++;if(AjaxRequest.numActiveAjaxGroupRequests[a.groupName]==1&&typeof(a.onGroupBegin)=="function"){a.onGroupBegin(a.groupName)}}if(typeof(a.onLoading)=="function"){a.onLoading(a)}a.onLoadingInternalHandled=true};a.onLoadedInternal=function(){if(a.onLoadedInternalHandled){return}if(typeof(a.onLoaded)=="function"){a.onLoaded(a)}a.onLoadedInternalHandled=true};a.onInteractiveInternal=function(){if(a.onInteractiveInternalHandled){return}if(typeof(a.onInteractive)=="function"){a.onInteractive(a)}a.onInteractiveInternalHandled=true};a.onCompleteInternal=function(){if(a.onCompleteInternalHandled||a.aborted){return}a.onCompleteInternalHandled=true;AjaxRequest.numActiveAjaxRequests--;if(AjaxRequest.numActiveAjaxRequests==0&&typeof(window.AjaxRequestEnd)=="function"){AjaxRequestEnd(a.groupName)}if(a.groupName!=null){AjaxRequest.numActiveAjaxGroupRequests[a.groupName]--;if(AjaxRequest.numActiveAjaxGroupRequests[a.groupName]==0&&typeof(a.onGroupEnd)=="function"){a.onGroupEnd(a.groupName)}}a.responseReceived=true;a.status=a.xmlHttpRequest.status;a.statusText=a.xmlHttpRequest.statusText;a.responseText=a.xmlHttpRequest.responseText;a.responseXML=a.xmlHttpRequest.responseXML;if(typeof(a.onComplete)=="function"){a.onComplete(a)}if(a.xmlHttpRequest.status==200&&typeof(a.onSuccess)=="function"){a.onSuccess(a)}else{if(typeof(a.onError)=="function"){a.onError(a)}}delete a.xmlHttpRequest.onreadystatechange;a.xmlHttpRequest=null};a.onTimeoutInternal=function(){if(a!=null&&a.xmlHttpRequest!=null&&!a.onCompleteInternalHandled){a.aborted=true;a.xmlHttpRequest.abort();AjaxRequest.numActiveAjaxRequests--;if(AjaxRequest.numActiveAjaxRequests==0&&typeof(window.AjaxRequestEnd)=="function"){AjaxRequestEnd(a.groupName)}if(a.groupName!=null){AjaxRequest.numActiveAjaxGroupRequests[a.groupName]--;if(AjaxRequest.numActiveAjaxGroupRequests[a.groupName]==0&&typeof(a.onGroupEnd)=="function"){a.onGroupEnd(a.groupName)}}if(typeof(a.onTimeout)=="function"){a.onTimeout(a)}delete a.xmlHttpRequest.onreadystatechange;a.xmlHttpRequest=null}};a.process=function(){if(a.xmlHttpRequest!=null){if(a.generateUniqueUrl&&a.method=="GET"){a.parameters.AjaxRequestUniqueId=new Date().getTime()+""+a.requestIndex}var c=null;for(var b in a.parameters){if(a.queryString.length>0){a.queryString+="&"}a.queryString+=encodeURIComponent(b)+"="+encodeURIComponent(a.parameters[b])}if(a.method=="GET"){if(a.queryString.length>0){a.url+=((a.url.indexOf("?")>-1)?"&":"?")+a.queryString}}a.xmlHttpRequest.open(a.method,a.url,a.async,a.username,a.password);if(a.method=="POST"){if(typeof(a.xmlHttpRequest.setRequestHeader)!="undefined"){a.xmlHttpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded")}c=a.queryString}if(a.timeout>0){setTimeout(a.onTimeoutInternal,a.timeout)}a.xmlHttpRequest.send(c)}};a.handleArguments=function(b){for(var c in b){if(typeof(a[c])=="undefined"){a.parameters[c]=b[c]}else{a[c]=b[c]}}};a.getAllResponseHeaders=function(){if(a.xmlHttpRequest!=null){if(a.responseReceived){return a.xmlHttpRequest.getAllResponseHeaders()}alert("Cannot getAllResponseHeaders because a response has not yet been received")}};a.getResponseHeader=function(b){if(a.xmlHttpRequest!=null){if(a.responseReceived){return a.xmlHttpRequest.getResponseHeader(b)}alert("Cannot getResponseHeader because a response has not yet been received")}};return a}AjaxRequest.getXmlHttpRequest=function(){if(window.XMLHttpRequest){return new XMLHttpRequest()}else{if(window.ActiveXObject){
/*@cc_on @*/
/*@if(@_jscript_version >=5)
try{return new ActiveXObject("Msxml2.XMLHTTP");}catch(e){try{return new ActiveXObject("Microsoft.XMLHTTP");}catch(E){return null;}}@end @*/
}else{return null}}};AjaxRequest.isActive=function(){return(AjaxRequest.numActiveAjaxRequests>0)};AjaxRequest.get=function(a){AjaxRequest.doRequest("GET",a)};AjaxRequest.post=function(a){AjaxRequest.doRequest("POST",a)};AjaxRequest.doRequest=function(c,a){if(typeof(a)!="undefined"&&a!=null){var b=new AjaxRequest();b.method=c;b.handleArguments(a);b.process()}};AjaxRequest.submit=function(a,b){var d=new AjaxRequest();if(d==null){return false}var c=AjaxRequest.serializeForm(a);d.method=a.method.toUpperCase();d.url=a.action;d.handleArguments(b);d.queryString=c;d.process();return true};AjaxRequest.serializeForm=function(b){var e=b.elements;var a=e.length;var g="";this.addField=function(h,i){if(g.length>0){g+="&"}g+=encodeURIComponent(h)+"="+encodeURIComponent(i)};for(var d=0;d<a;d++){var f=e[d];if(!f.disabled){switch(f.type){case"text":case"password":case"hidden":case"textarea":this.addField(f.name,f.value);break;case"select-one":if(f.selectedIndex>=0){this.addField(f.name,f.options[f.selectedIndex].value)}break;case"select-multiple":for(var c=0;c<f.options.length;c++){if(f.options[c].selected){this.addField(f.name,f.options[c].value)}}break;case"checkbox":case"radio":if(f.checked){this.addField(f.name,f.value)}break}}}return g};AjaxRequest.numActiveAjaxRequests=0;AjaxRequest.numActiveAjaxGroupRequests=new Object();AjaxRequest.numAjaxRequests=0;
/*! JSON fallback Author: https://github.com/douglascrockford/JSON-js */
var JSON;if(!JSON){JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==="string"){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());
47 changes: 47 additions & 0 deletions lib/mustekala.js
@@ -0,0 +1,47 @@
module.exports=function() {
var m={
version: '0.1.0'
,github: 'http://github.com/daraosn/mustekala'
,config: {}
,io: {}
,socket: {}
}

try {
m.config = require('../config.js');
} catch(e) {
console.log("No 'config.js' found. Use 'config.template.js' to create one.");
process.exit(1);
}

m.trigger=function(password, channel, action, data) {
if(password==mustekala.config.password) {
if(channel) {
mustekala.socket.to(channel).emit('trigger', channel, action, data);
} else {
mustekala.socket.emit('trigger', channel, action, data);
}
return true;
} else {
return false;
}
}

m.socketPresencePreauthorize=function(password) {
if(password==mustekala.config.password) {
do { var token = global.token(); } while(mustekala.authKeys[token]);
mustekala.authKeys[token] = new Date().getTime() + presenceKeyExpiry;
console.log('@@@@@'+global.dump(mustekala.authKeys));
return token;
} else {
return false;
}
}

m.initialize=function() {
require('./routes');
require('./configure');
require('./socket');
}
return m;
}
73 changes: 73 additions & 0 deletions lib/routes.js
@@ -0,0 +1,73 @@
app.post('/mustekala/trigger', function(req, res) {
var password=req.body.password;
var channel=req.body.channel;
var action=req.body.action;
var data=req.body.data;
var result=mustekala.trigger(password, channel, action, data);
// console.log({'password': password, 'channel': channel, 'action': action, 'data': data})
res.send(JSON.stringify({'success':result, 'channel': channel, 'action': action, 'data': data}));
});

app.post('/mustekala/authentificate', function(req, res) {
var password=req.body.password;
var channel=req.body.channel;
var user=req.body.channel;

});

app.get('/mustekala.js', function(req, res) {
// we must "cheat" socket.io to get the socket.io.js with specific transports
var ioHead={};
var ioEnd={};
var fakeRes = {
writeHead: function(status, headers) {
ioHead.status=status;
ioHead.headers=headers;
},
end: function(content, encoding) {
ioEnd.content=content;
ioEnd.encoding=encoding;
finishRequest();
}
}
// fake uri
req.url="/socket.io/socket.io.js";
var data=mustekala.io.checkRequest(req);
var mustekalaJS=require('fs').readFileSync(process.cwd()+'/lib/mustekala.client.js');
mustekala.io.static.write(data.path, req, fakeRes);
// send js
function finishRequest() {
// inject mustekala
ioEnd.content=mustekalaJS+"\n"+ioEnd.content;
ioHead.headers['Content-Length']=ioEnd.content.length;
res.writeHead(ioHead.status, ioHead.headers)
res.end(ioEnd.content, ioEnd.encoding);
}
// TODO: verify if socket.io.js is dynamic or not to cache js.
});

app.get('/', function(req, res) {
res.writeHead(200, {"Content-Type": "text/html"});
res.end('Mustekala v'+mustekala.version+'. Fork it at <a href="'+mustekala.github+'">'+mustekala.github+'</a>');
});

// TODO: Disable on production
app.post('/example/auth', function(req, res) {
var exampleUser = {
id: '12345'
,data: {
name: 'Joseph'
,nickname: 'joe'
}
}
require('request').post({
url: '/mustekala/authentificate'
,body: {
password: mustekala.config.password
,user: exampleUser
}
},function(error, response, body) {
console.log(body);
});
res.end(JSON.stringify({'authKey': '12345'}));
});

0 comments on commit c8e8c69

Please sign in to comment.