aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--chatserver.html211
-rwxr-xr-xchatserver.js165
3 files changed, 377 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/chatserver.html b/chatserver.html
new file mode 100644
index 0000000..073ae2d
--- /dev/null
+++ b/chatserver.html
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Chatserver</title>
+<script>
+var ws,nick;
+ws=new WebSocket("ws://"+location.hostname+":8000");
+ws.addEventListener("message",function(msg){
+ var cmd=msg.data.split(" ");
+ //console.log(msg);
+ if(msg.data[0]=="<")addMessageLine(cmd[0].slice(1),msg.data.slice(cmd[0].length+1));
+ else if(cmd[0]=="#yournick")nick=cmd[1];
+ else if(cmd[0]=="#lobbylist")updateLobbyList(cmd.slice(1));
+ else if(cmd[0]=="#nicklist")updateNickList(cmd.slice(1));
+ else if(cmd[0]=="#nick"){
+ addMessageLine("--","<"+cmd[1]+"> is now known as <"+cmd[2]+">");
+ if(cmd[1]==nick)nick=cmd[2];
+ } else if(cmd[0]=="#join")addMessageLine("--","<"+cmd[1]+"> joined the lobby");
+ else if(cmd[0]=="#part")addMessageLine("--","<"+cmd[1]+"> left the lobby");
+ else if(cmd[0]=="#lobby")highlightLobby(cmd[1]);
+ else if(cmd[0]=="#historystart")clearMessageHistory();
+ else if(cmd[0]=="#history"){
+ if(cmd[1]==">")addMessageLine(cmd[2],cmd.slice(3).join(" "),true);
+ else if(cmd[1]=="join")addMessageLine("--","<"+cmd[2]+"> joined the lobby",true);
+ else if(cmd[1]=="part")addMessageLine("--","<"+cmd[2]+"> left the lobby",true);
+ else addMessageLine("-?-","UNKNOWN HISTORY EVENT "+cmd[2]);
+ } else if(cmd[0]=="#historyend");
+ else if(cmd[0]=="#error")alert("Error: "+msg.data.slice(7));
+});
+ws.addEventListener("open",function(){
+ ws.send("#lobbylist");
+});
+
+function highlightLobby(id){
+ var i,l=document.querySelectorAll("#lobbylistdiv>div");
+ for(i=0;i<l.length;i++){
+ if(l[i].getAttribute("lobbyid")==id)l[i].classList.add("selected");
+ else l[i].classList.remove("selected");
+ }
+}
+
+function updateLobbyList(list){
+ var container=document.getElementById("lobbylistdiv"),div,i;
+ container.innerHTML="";
+ for(i=0;i<list.length;i++){
+ (function(i){
+ div=document.createElement("div");
+ div.appendChild(document.createTextNode(list[i].slice(list[i].indexOf(":")+1)));
+ div.setAttribute("lobbyid",list[i].slice(0,list[i].indexOf(":")));
+ div.addEventListener("click",function(ev){
+ ws.send("#lobby "+ev.target.getAttribute("lobbyid"));
+ },false);
+ container.appendChild(div);
+ })(i);
+ }
+}
+
+function clearMessageHistory(){
+ var tb=document.getElementById("lobbylogtb"),
+ l=document.querySelectorAll("#lobbylogtb>tr"),
+ i;
+ for(i=0;i<l.length;i++)tb.removeChild(l[i]);
+}
+
+function addMessageLine(from,msg,isHistory){
+ var tr,td,table,tbody,tr2,td2;
+ tr=document.createElement("tr");
+ td=document.createElement("td");
+ table=document.createElement("table");
+ tbody=document.createElement("tbody");
+ tr2=document.createElement("tr");
+
+ td2=document.createElement("td");
+ td2.appendChild(document.createTextNode(from=="--"||from=="*"?from:"<"+from+">"));
+ if(from==nick)td.style.color="#00f";
+ tr2.appendChild(td2);
+
+ td2=document.createElement("td");
+ td2.appendChild(document.createTextNode(msg));
+ tr2.appendChild(td2);
+
+ tbody.appendChild(tr2);
+ table.appendChild(tbody);
+ td.appendChild(table);
+ tr.appendChild(td);
+
+ if(isHistory)tr.style.backgroundColor="#ccc";
+
+ document.getElementById("lobbylogtb").appendChild(tr);
+
+ if(tr.getBoundingClientRect&&document.documentElement&&document.documentElement.clientHeight&&tr.getBoundingClientRect().top<document.documentElement.clientHeight+200)
+ tr.scrollIntoView({block:"end",smooth:true});
+}
+
+function sendMessage(text){
+ ws.send(">"+text);
+}
+
+function changeNickPrompt(){
+ var newnick=prompt("If you want to change your nick, please enter your new choice here.");
+ if(newnick!=null)ws.send("#nick "+newnick.replace(/[\x00-\x20\x7F]/g,""));
+}
+</script>
+<style>
+html{height:calc(100% - 8px);}
+body{
+ height:calc(100% - 8px);
+ margin:8px;
+}
+table#maintable,
+table#maintable > tbody,
+table#maintable > tbody > tr{
+ width:800px;
+ height:100%;
+ display:block;
+ margin:0;
+ padding:0;
+}
+td#lobbylist,
+td#lobbylog{
+ display:inline-block;
+ height:calc(100% - 8px);
+ border:1px #ccc solid;
+ border-radius:10px;
+ padding:0;
+ font-family:Monaco,"Courier New",Monospace;
+}
+td#lobbylist{width:120px;}
+td#lobbylog{
+ width:672px;
+ word-wrap:break-word;
+}
+div#lobbylistdiv{
+ width:100%;
+ height:calc(100% - 30px);
+}
+div#lobbylistdiv > div{
+ cursor:pointer;
+}
+div#lobbylistdiv > div.selected{
+ color:#00a;
+ background-color:#eef;
+}
+td#lobbylog > div,
+tbody#lobbtlobtb{
+ overflow:scroll;
+}
+tbody#lobbylogtb,
+tbody#lobbylogtb > tr{
+ width:100%;
+}
+td#lobbylog > div{
+ height:calc(100% - 22px);
+}
+tbody#lobbylogtb td{
+ padding:0;
+}
+td#lobbylog table{
+ border-collapse:collapse;
+}
+tbody#lobbylogtb > tr > td > table > tbody > tr > td:first-child{
+ display:inline-block;
+ padding-right:6px;
+ width:100px;
+ text-align:right;
+ margin-bottom:5px;
+}
+tbody#lobbylogtb > tr > td > table > tbody > tr > td:last-child{
+ /*display:inline-block;*/
+ padding-left:6px;
+ width:calc(100% - 100px);
+ max-width:calc(100% - 100px);
+ margin-bottom:5px;
+ word-wrap:break-word;
+ border-left:1px #eee solid;
+}
+div#nickbtn{
+ display:inline-block;
+ background-color:#bbb;
+ border:1px #ddd solid;
+ border-radius:5px;
+ font-size:12px;
+ cursor:pointer;
+}
+
+.textinput{
+ bottom:0;
+ width:664px;
+ height:15px;
+ border:none;
+ border-top:1px #aaa solid;
+ font-size:14px;
+}
+</style>
+</head>
+<body>
+<table id="maintable"><tbody><tr>
+ <td id="lobbylist">
+ <div id="lobbylistdiv"></div>
+ <div>
+ <div id="nickbtn" onclick="changeNickPrompt()">change nick</div>
+ </div>
+ </td>
+ <td id="lobbylog">
+ <div><table><tbody id="lobbylogtb"></tbody></table></div>
+ <input type="text" class="textinput" placeholder="..." onkeypress="if(event.keyCode==13){sendMessage(this.value);this.value='';}">
+ </td>
+</tr></tbody></table>
+</body>
+</html> \ No newline at end of file
diff --git a/chatserver.js b/chatserver.js
new file mode 100755
index 0000000..0529630
--- /dev/null
+++ b/chatserver.js
@@ -0,0 +1,165 @@
+#!/usr/bin/env node
+var fs=require("fs"),
+ http=require("http"),
+ WebSocketServer=require("ws").Server,
+ CircularBuffer=require("circular-buffer");
+var HTTPPORT=81,WSPORT=8000;
+
+
+////////// WEBSOCKETS //////////
+
+function uniqid(){
+ if(!uniqid.$)uniqid.$=1;
+ return uniqid.$++;
+}
+
+var users=[];
+var lobbies=[];
+
+function lobby_create(lname){
+ lobbies.push({id:uniqid(),name:lname,users:[],history:new CircularBuffer(10)});
+}
+
+["a_lobby","lobby_2"].forEach(lobby_create);
+
+new WebSocketServer({port:WSPORT}).on("connection",function(ws){
+ var uobj;
+ (function(){
+ var nick="_user"+uniqid();
+ while(fnick(nick)!=-1)nick="_user"+uniqid();
+ uobj=new UserObject(nick,ws);
+ })();
+ users.push(uobj);
+ ws.send("#yournick "+uobj.nick);
+ ws.on("message",function(msg,flags){
+ console.log("("+uobj.id+","+uobj.nick+"): "+msg);
+ if(msg.match(/^#lobby /)){
+ var which=parseInt(msg.slice(7),10),lobidx=flob(which),oldlobidx;
+ if(lobidx==-1){
+ ws.send("#error Invalid lobby");
+ return;
+ }
+ ws.send("#lobby "+which);
+ if(uobj.lob!=null){
+ oldlobidx=flob(uobj.lob);
+ lobbies[oldlobidx].users.splice(lobfuser(lobbies[oldlobidx],uobj.id),1);
+ lobbysend(lobbies[oldlobidx],"#part "+uobj.nick);
+ lobbies[oldlobidx].history.push(["part",uobj.nick]);
+ }
+ uobj.lob=which;
+ lobbies[lobidx].users.push(uobj.id);
+ lobbysend(lobbies[lobidx],"#join "+uobj.nick);
+ lobbies[lobidx].history.push(["join",uobj.nick]);
+ uobj.ws.send("#historystart");
+ lobbies[lobidx].history.toarray().forEach(function(h){
+ uobj.ws.send("#history "+h.join(" "));
+ });
+ uobj.ws.send("#historyend");
+ } else if(msg.match(/^#nick /)){
+ var choice=msg.slice(6).replace(/[\x00-\x20\x7F\x80-\xFF]/g,""); //all shitty characters including space (' ')
+ var i;
+ if(choice.length==0){
+ ws.send("#error Invalid nick");
+ return;
+ }
+ if(uobj.lob!=null){
+ if(fnick(choice)!=-1){
+ i=1;
+ while(fnick(choice+i)!=-1)i++;
+ choice+=i;
+ }
+ lobbysend(lobbies[flob(uobj.lob)],"#nick "+uobj.nick+" "+choice);
+ }
+ uobj.nick=choice;
+ } else if(msg.match(/^#lobbylist/)){
+ ws.send("#lobbylist "+lobbies.map(function(lob){return lob.id+":"+lob.name;}).join(" "));
+ } else if(msg.match(/^#nicklist/)){
+ if(this.lob==null){
+ ws.send("#error Cannot request nicklist while not in a lobby");
+ return;
+ }
+ ws.send("#nicklist "+lobbies[flob(uobj.lob)].users.map(function(usr){
+ return users[fuser(usr)].nick;
+ }).join(" "));
+ } else if(msg[0]==">"){ //message!
+ var lobidx;
+ if(uobj.lob==null){
+ ws.send("#error Cannot send message while not in a lobby");
+ return;
+ }
+ lobidx=flob(uobj.lob);
+ lobbysend(lobbies[lobidx],"<"+uobj.nick+" "+msg.slice(1));
+ lobbies[lobidx].history.push([">",uobj.nick,msg.slice(1)]);
+ } else {
+ ws.send("#error Invalid message starting with \""+msg.slice(0,10)+"\"");
+ return;
+ }
+ });
+ ws.on("close",function(){
+ var lobidx;
+ if(uobj.lob!=null){
+ lobidx=flob(uobj.lob);
+ lobbysend(lobbies[lobidx],"#part "+uobj.nick,[uobj.id]);
+ lobbies[lobidx].history.push(["part",uobj.nick]);
+ lobbies[lobidx].users.splice(lobfuser(lobbies[lobidx],uobj.id),1);
+ }
+ users.splice(fuser(uobj.id),1);
+ });
+});
+console.log("Websocket server started on port "+WSPORT+".");
+
+function UserObject(nick,ws){
+ if(!(this instanceof UserObject))return new UserObject(nick,ws);
+ this.id=uniqid();
+ this.nick=nick;
+ this.ws=ws;
+ this.lob=null;
+}
+
+function lobbysend(lob,msg,exceptids){
+ var uidx;
+ if(exceptids==undefined)exceptids=[];
+ for(var i=0;i<lob.users.length;i++){
+ if(exceptids.indexOf(lob.users[i])==-1){
+ uidx=fuser(lob.users[i]);
+ users[uidx].ws.send(msg);
+ }
+ }
+}
+
+function lobfuser(lob,id){
+ for(var i=0;i<lob.users.length;i++)if(users[fuser(lob.users[i])].id==id)return i;
+ return -1;
+}
+
+function fuser(id){
+ for(var i=0;i<users.length;i++)if(users[i].id==id)return i;
+ return -1;
+}
+function fnick(nick){
+ for(var i=0;i<users.length;i++)if(users[i].nick==nick)return i;
+ return -1;
+}
+
+function flob(lobid){
+ for(var i=0;i<lobbies.length;i++)if(lobbies[i].id==lobid)return i;
+ return -1;
+}
+
+
+////////// HTTP //////////
+
+var pagehtml;
+try{
+ pagehtml=fs.readFileSync("chatserver.html").toString();
+} catch(e){
+ console.log("Could not read file 'chatserver.html'.");
+ console.log(e);
+ process.exit();
+}
+
+http.createServer(function(req,res){
+ res.writeHead(200,{"Content-Type":"text/html"});
+ res.end(pagehtml);
+}).listen(HTTPPORT);
+console.log("HTTP server started on port "+HTTPPORT+".");