diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | chatserver.html | 211 | ||||
-rwxr-xr-x | chatserver.js | 165 |
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+"."); |