Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Added a second example using channels and has a basic API #52

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions example2/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Channel or Chat Room Example.
===========

This is an extension of the original chat example showcasing how one may want to
use channels for communication versus a catch all approach.

The user is asked for their username and the channels they want to subscribe to.

If users are connected to at least one common channel then they can chat with each
other. Channels can be entered in comma delimeted form.

There is also an example of two APIs
/api/who - responds with the usernames of who is connected in JSON
/api/send - Allows you to send data to a channel. You need to specify the
following parameters (username, message, publish) where publish is
the channel list.

Note: This example is lacking the buffering that is used in the first example.
109 changes: 109 additions & 0 deletions example2/chat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<html>
<head>
<title>socket.io client test</title>

<script src="/json.js"></script> <!-- for ie -->
<script src="/socket.io/socket.io.js"></script>
</head>
<body>

<script>
function message(obj){
var el = document.createElement('p');
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
document.getElementById('chat').appendChild(el);
document.getElementById('chat').scrollTop = 1000000;
}

function send(){
var obj = {publish: null, message: null};
obj.publish = document.getElementById('channels').value.replace(/[ ]*,[ ]*/g,',').split(',');
obj.message = document.getElementById('text').value;
socket.send(obj);
document.getElementById('text').value = '';
if(window.console) console.log(obj);
}

function esc(msg){
return msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};

var socket = new io.Socket(null, {port: 8080});
socket.connect();
socket.on('message', function(obj){
if ('subscribed' in obj){
handleSubscription(obj);
} else if ('json' in obj){
handleJSON(obj);
} else if ('message' in obj || 'announcement' in obj) {
message(obj);
}
});

function handleSubscription(obj) {
document.getElementById('chat_form').style.display='block';
document.getElementById('chat').innerHTML = '';
}

function handleJSON(obj){
var el = document.createElement('p');
if(window.console) console.log("JSON Rx: From - " + obj.message[0] + ", " + obj.message[1]);
}

function subscribe(){
var obj = {subscribe: null, username: null};
obj.username = document.getElementById('username').value;
obj.subscribe = document.getElementById('channels').value.replace(/[ ]*,[ ]*/g,',').split(',');

if(window.console) console.log(obj);
socket.send(obj);
document.getElementById('chat').style.display='block';
document.getElementById('channel_form').style.display='none';
return false;
}

function next(){
document.getElementById('channel_form').style.display='block';
document.getElementById('name_form').style.display='none';
}

</script>

<!-- USER NAME SELECTION -->
<form id="name_form" class="form" onsubmit="next(); return false;">
Username: <input id="username" class="text"type="text" name="username" value=""/>
<input id="nameEnterBtn" class="button" type="submit" name="" value="Next"/>
</form>

<!-- CHANNEL SELECTION -->
<form id="channel_form" class="form" onsubmit="subscribe(); return false;">
Channels: <input id="channels" class="text"type="text" name="channels" value=""/>
<input id="channelEnterBtn" class="button" type="submit" name="" value="Join"/>
</form>

<!-- CHAT UI -->
<div id="chat"><p>Connecting...</p></div>
<form id="chat_form" class="form" onsubmit="send(); return false;">
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
</form>

<style>
#chat { display: none;}
#chat { height: 300px; overflow: auto; width: 500px; border: 1px solid #eee; font: 13px Helvetica, Arial; }
#chat p { padding: 8px; margin: 0; }
#chat p:nth-child(odd) { background: #F6F6F6; }
#name_form { width: 482px; text-align: right; }
#name_form input[type=text] { width: 140px; padding: 5px; background: #fff; border: 1px solid #fff; }
#channel_form { display: none; }
#channel_form input[type=text] { width: 345px; padding: 5px; background: #fff; border: 1px solid #fff; }
#chat_form { display: none; }
.form { color: #FFFFFF; width: 482px; background: #333; padding: 5px 10px; }
.form input[type=text] { width: 416px; padding: 5px; background: #fff; border: 1px solid #fff; }
.form input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; }
.form input[type=submit]:hover { background: #A2A2A2; }
.form input[type=submit]:active { position: relative; top: 2px; }
</style>

</body>
</html>
18 changes: 18 additions & 0 deletions example2/json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}
Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
return'['+a.join(',')+']';}
if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
return'{'+a.join(',')+'}';}}
return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
return filter(k,v);}
if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
throw new SyntaxError('parseJSON');}};}();}
178 changes: 178 additions & 0 deletions example2/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
var http = require('http'),
url = require('url'),
fs = require('fs'),
io = require('../'),
sys = require('sys'),
qs = require("querystring"),

send404 = function(res){
res.writeHead(404);
res.write('404');
res.end();
},

sendJSON = function (res, code, obj) {
var body = JSON.stringify(obj);
res.writeHead(code, { "Content-Type": "text/json"
, "Content-Length": body.length
});
res.end(body);
},


/************************************************************************
* Subscriptions manages the connected clients. A client will generally be
* connected longer than a callback is so we need to manage them. Each
* time a callback connects with a particular ID we poke its timestamp.
* Subscriptions will get removed if their timestamp is too old.
************************************************************************/
subscriptions = {
memberships: [],
channels: {},

newSubscription: function (client, username, channelNames) {
if (username.length > 50) return null;
if (/[^\w_\-^!]/.exec(username)) return null;

// Create or add to the channel and add the client to it.
for (var i=0; i< channelNames.length; i++) {
cn = channelNames[i];
if (!subscriptions.channels[cn]) subscriptions.channels[cn] = [];
subscriptions.channels[cn].push(client);
}

// Store the details of the subscription
var membership = {
username: username,
channels: channelNames,
id: client.sessionId,
timestamp: new Date(),

poke: function () {
membership.timestamp = new Date();
},

destroy: function () {
var clientChannels = subscriptions.memberships[client.sessionId].channels;
for (var i=0; i< clientChannels.length; i++) {
var channel = clientChannels[i],
idx = subscriptions.channels[channel].indexOf(client);
subscriptions.channels[channel].splice(idx,1);
}
delete subscriptions.memberships[client.sessionId];
}
};

subscriptions.memberships[membership.id] = membership;
},

publish: function (channels,message) {
var receivedClients = [];
for (var i = 0; i < channels.length; i++) {
var clients = this.channels[channels[i]];
if (clients === undefined) continue;
for (var j = 0; j < clients.length; j++) {
if (receivedClients.indexOf(clients[j]) > -1) continue;
clients[j].send(message);
receivedClients.push(clients[j]);
}
}
}
},

processMessage = function(client,obj){
if (client != null && obj.subscribe && obj.username) {
sys.log("Rx: subscribing to " +obj.subscribe+ " by "+obj.username);
subscriptions.newSubscription(client,obj.username,obj.subscribe);
var membership = subscriptions.memberships[client.sessionId],
message = { announcement: membership.username + ' connected' };
subscriptions.publish(membership.channels,message);
client.send({ subscribed: true });
} else if (client != null && obj.publish && obj.message) {
var membership = subscriptions.memberships[client.sessionId],
message = {message: [membership.username, obj.message]};
sys.log("Rx: publishing to " +obj.publish+ " by "+membership.username);
subscriptions.publish(obj.publish,message);
} else if (client == null && obj.publish && obj.message && obj.username) {
var message = {message: [obj.username, obj.message]};
sys.log("Rx: publishing TEXT to " +obj.publish+ " by "+obj.username);
subscriptions.publish(obj.publish,message);
} else if (client == null && obj.publish && obj.json && obj.username) {
var message = {json: [obj.username, obj.json]};
sys.log("Rx: publishing JSON to " +obj.publish+ " by "+obj.username);
subscriptions.publish(obj.publish,message);
} else {
var obj_str = [],
error = new Error("API:processMessage:unknownParams -");
for(a in obj) obj_str.push(" obj["+ a + "] => " + obj[a]);
error.message += obj_str.join();
error['code'] = 100;
throw error;
}
},

server = http.createServer(function(req, res){
// your normal server code
var path = url.parse(req.url).pathname;
switch (path){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
res.end();
break;
case '/json.js':
case '/chat.html':
fs.readFile(__dirname + path, function(err, data){
if (err) return send404(res);
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
res.write(data, 'utf8');
res.end();
});
break;
case "/api/who":
var usernames = [];
for (var id in subscriptions.memberships) {
if (!subscriptions.memberships.hasOwnProperty(id)) continue;
var membership = subscriptions.memberships[id];
usernames.push(membership.username);
}
sendJSON(res, 200, { usernames: usernames, stat: "ok" });
break;

case "/api/send":
var obj = {
username: qs.parse(url.parse(req.url).query).username,
message: qs.parse(url.parse(req.url).query).text,
publish: qs.parse(url.parse(req.url).query).channels
}

try {
processMessage(null,obj);
sendJSON(res,200,{stat: "ok"});
} catch(e) {
sendJSON(res,200,{stat: "fail", code: e.code, message: e.message});
}
break;
default: send404(res);
}
});

server.listen(8080);
var io = io.listen(server);

io.on('connection', function(client){

client.on('message', function(obj) {
processMessage(client,obj);
});

client.on('disconnect', function(){
if (subscriptions.memberships.hasOwnProperty(client.sessionId)) {
var membership = subscriptions.memberships[client.sessionId],
message = { announcement: membership.username + ' disconnected' };
subscriptions.publish(membership.channels,message);
subscriptions.memberships[client.sessionId].destroy();
}
});

});