<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <!-- reliable-chat - multipath chat Copyright (C) 2012 Scott Worley <sworley@chkno.net> Copyright (C) 2012 Jason Hibbs <skitch@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. --> <head> <title>Reliable Chat</title> <style type="text/css"><!--/*--><![CDATA[/*><!--*/ html, body { width: 99.9%; height: 100%; margin: 0; padding: 0; background-color: #293134; color: silver; font-family: monospace; } #container { height: 100%; } #status { width: 100%; text-align: right; background-color: #293134; padding: 5px 5px 5px 0px; } #client { width: 98.5%; padding: 0px 0px 0px 5px; height: 50px; position: fixed; bottom: 0; } #input { width: 100%; background-color: #293134; } #say { width: 100% } #history { padding: 0px 5px 55px 5px; vertical-align: bottom } img { width: 1px; height: 1px; } iframe { display: none } #status span { margin-right: 10px; } #status span.sad { background-color: #f00; color: #fff; border: 1px solid black; border-radius: 5px; padding-left: 5px; padding-right: 5px; } #status span.happy { background-color: #0f0; color: #000; border: 1px solid black; border-radius: 5px; padding-left: 5px; padding-right: 5px; } /*]]>*/--></style> <script type="text/javascript"><!--//--><![CDATA[//><!-- var servers = ['chkno.net', 'rc2.chkno.net', 'echto.net', 'the-wes.com', 'vibrantlogic.com']; var session = Math.random(); // For outgoing message IDs var since = {}; // server -> time: For fetch?since= var seen = {}; // seen_key -> true function rcnick() { var nick = localStorage.getItem("nick"); if (nick) { return nick; } return 'anonymous'; } function rcsetnick(new_nick) { localStorage.setItem("nick", new_nick); } function rcserverbase(server) { // Add the default port if server doesn't contain a port number already if (server.indexOf(":") == -1) { return "http://" + server + ":21059"; } else { return "http://" + server; } } function rcchangeserverstatus(server, new_status) { var statusbar = document.getElementById("status"); var spans = statusbar.getElementsByTagName("span"); for (var i in spans) { if (spans[i].firstChild && 'data' in spans[i].firstChild && spans[i].firstChild.data == server) { spans[i].setAttribute("class", new_status); } } } function rcformattime(t) { var d = t.getDay(); d = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d]; var h = t.getHours(); var m = t.getMinutes(); var s = t.getSeconds(); function pad(x) { return (x < 10 ? "0" : "") + x; } return d + " " + pad(h) + ":" + pad(m) + ":" + pad(s); } function rcaddmessagetohistory(message) { var d = document.createElement("div"); var text = (message.Time ? rcformattime(message.Time) : "") + " " + message.Text; d.appendChild(document.createTextNode(text)); var h = document.getElementById("history"); h.appendChild(d); window.scrollTo(0, document.body.scrollHeight); return d; } function make_seen_key(id, text) { return id.replace(/@/g, "@@") + "_@_" + text.replace(/@/g, "@@"); } function rcreceivemessages(server, messages) { for (var i in messages) { var m = messages[i]; m.Time = new Date(m.Time); var seen_key = make_seen_key(m.ID, m.Text); if (!(seen_key in seen)) { seen[seen_key] = true; rcaddmessagetohistory(m); for (var i in servers) { rcchangeserverstatus(servers[i], "sad"); } } rcchangeserverstatus(server, "happy"); } } function rcfetch(server) { var delay = 10000; // TODO: Exponential backoff var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == this.DONE) { if (this.status == 200) { var rtxt = this.responseText; if (rtxt != null) { var messages = JSON.parse(rtxt); if (messages != null) { delay = 40; if (messages.length >= 1 && "Time" in messages[messages.length-1]) { since[server] = messages[messages.length-1].Time; } rcreceivemessages(server, messages); } } } window.setTimeout(rcfetch, delay, server); } } var uri = rcserverbase(server) + "/fetch"; if (server in since) { uri += '?since="' + since[server] + '"'; } xhr.open("GET", uri); xhr.send(); } function rcconnect() { for (var i in servers) { rcfetch(servers[i]); // Status bar entry var status_indicator = document.createElement("span"); status_indicator.appendChild(document.createTextNode(servers[i])); status_indicator.setAttribute("class", "sad"); document.getElementById("status").appendChild(status_indicator); } } function rcsend(d, message) { var id = new Date().getTime() + "-" + session + "-" + Math.random(); seen[make_seen_key(id, message)] = true; var path = "/speak" + "?id=" + encodeURIComponent(id) + "&text=" + encodeURIComponent(message); for (var i in servers) { var uri = rcserverbase(servers[i]) + path; var img = document.createElement("img"); img.setAttribute("src", uri); d.appendChild(img); } } function rcinput(input) { var message; var re = /^\/([a-z]+) (.*)/ var match = re.exec(input); if (match && match[1] == 'me') { message = "* " + rcnick() + " " + match[2]; } else if (match && match[1] == 'nick') { message = "*** " + rcnick() + " is now known as " + match[2]; rcsetnick(match[2]); } else { message = "<" + rcnick() + "> " + input; } var d = rcaddmessagetohistory({'Text': message}); rcsend(d, message); } function rckeydown(event) { if (event.keyCode == 13) { rcinput(document.input.say.value); document.input.say.value = ""; } } //--><!]]></script> </head> <body onload="rcconnect()"> <div id="container"> <div id="history"></div> <div id="client"> <div id="input"> <form name="input" onsubmit="return false" autocomplete="off"> <input id="say" onkeydown="return rckeydown(event)" autocomplete="off" autofocus="autofocus"></input> </form></div> <div id="status"> </div> </div> </div> </body> </html>