diff --git a/websocks-demo/index.html b/websocks-demo/index.html
new file mode 100644
index 0000000..ea56a1d
--- /dev/null
+++ b/websocks-demo/index.html
@@ -0,0 +1,14 @@
+
+
+
+ irc client with WebSOCKS
+
+
+
+
+
+
+
+
+
+
diff --git a/websocks-demo/style.css b/websocks-demo/style.css
new file mode 100644
index 0000000..5b1ea8a
--- /dev/null
+++ b/websocks-demo/style.css
@@ -0,0 +1,23 @@
+
+.text-window {
+ position: fixed;
+ height: 90%;
+ width: 100%;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ overflow-y: scroll;
+}
+
+.text-input {
+ width: 100%;
+ position: fixed;
+ bottom: 0px;
+ left: 10px;
+}
+
+#irc-window {
+ position: fixed;
+ width: 95%;
+ top: 20px;
+ left: 10px;
+}
\ No newline at end of file
diff --git a/websocks-demo/websocks.js b/websocks-demo/websocks.js
new file mode 100644
index 0000000..3b9850a
--- /dev/null
+++ b/websocks-demo/websocks.js
@@ -0,0 +1,293 @@
+
+function ws_try_connect()
+{
+
+
+ var elem = document.getElementById("irc-window");
+ elem.remove();
+ elem = document.createElement("div");
+ elem.id ="irc-window";
+ document.body.appendChild(elem);
+
+ var input = document.createElement("input");
+ input.setAttribute("class", "text-input");
+ elem.appendChild(input);
+
+
+ var irc = {
+ connected: 0,
+ line_in: "",
+ target: "",
+ nick: "ebin",
+ panes: {},
+ };
+
+ function irc_ui_show_pane(pane)
+ {
+ irc_ui_ensure_pane(pane);
+ // hide all other panes
+ for (var k in irc.panes) {
+ if(k == pane) {
+ irc.panes[k].elem.style = "display: inline-block";
+ irc.target = k;
+ } else {
+ irc.panes[k].elem.style = "display: none";
+ }
+ }
+ }
+
+ function irc_ui_ensure_pane(pane)
+ {
+ if(irc.panes[pane]) return;
+ var e = document.createElement("pre");
+ e.setAttribute("class", "text-window");
+ e.style = "display: none";
+ e.setAttribute("panename", pane);
+ irc.panes[pane] = {
+ elem: e,
+ name: pane,
+ };
+ var root = document.getElementById("irc-window");
+ root.appendChild(e);
+ }
+
+ function irc_ui_println(line, pane)
+ {
+ if(line == "") return;
+ var node = document.createTextNode(line);
+ var e = document.createElement("div");
+ e.appendChild(node);
+ if(!pane) {
+ pane = " ";
+ }
+ irc_ui_ensure_pane(pane);
+ var p = irc.panes[pane];
+ if(p) {
+ p.elem.appendChild(e);
+ if (pane == irc.target) {
+ window.scroll(0, p.elem.offsetTop + p.elem.offsetHeight);
+ }
+ } else {
+ console.log("No pane called "+pane);
+ }
+
+ }
+
+ function irc_on_privmsg(src, target, msg)
+ {
+ var parts = src.split("!");
+ src = parts[0].slice(1);
+ irc_ui_println("<"+src+ "> "+msg, target);
+ }
+
+ function irc_on_greeted(conn)
+ {
+ irc_ui_println("successfully joined irc");
+ }
+
+ function irc_on_join(src, target)
+ {
+ irc_ui_println("--> "+src, target);
+ }
+
+ function irc_on_part(src, target)
+ {
+ irc_ui_println("<-- "+src, target);
+ }
+
+ function irc_on_other(src, cmd, target, msg)
+ {
+ irc_ui_println("<"+src+"> "+msg, src);
+ }
+
+ function irc_process_in(conn)
+ {
+ var line = irc.line_in.trim();
+ console.log("--> "+line);
+ if(line.startsWith("PING ")) {
+ // handle ping
+ irc_sendline(conn, "PONG "+line.slice(5));
+ return;
+ }
+ var parts = line.split(" ");
+ if (parts.length > 2) {
+ var src = parts[0];
+ var cmd = parts[1];
+ var target = parts[2];
+ var idx = line.indexOf(target);
+ var msg = line.slice(idx+target.length+2);
+ if (cmd == "PRIVMSG") {
+ irc_on_privmsg(src, target, msg);
+ return;
+ }
+ if (cmd == "JOIN" ) {
+ irc_on_join(src, target);
+ return;
+ }
+ if (cmd == "PART") {
+ irc_on_part(src, target);
+ return;
+ }
+
+ if(cmd == "PONG") return;
+ if(cmd == "376") {
+ // we have been greeted fully
+ irc_on_greeted(conn);
+ return;
+ }
+ irc_on_other(src.slice(1), cmd, target, msg);
+ }
+ irc_ui_println(line);
+ }
+
+ function irc_data(conn, data)
+ {
+ data = irc.line_in + data;
+ var lines = data.split("\n");
+ for(var idx = 0; idx < lines.length; idx++) {
+ irc.line_in = lines [idx] + "\n";
+ irc_process_in(conn);
+ }
+ }
+
+ function irc_sendline(conn, line)
+ {
+ console.log("<-- "+ line);
+ conn.send(line + "\n");
+ }
+
+ function irc_privmsg(conn, target, msg)
+ {
+ irc_ui_println("<"+irc.nick+"> "+msg, target);
+ irc_sendline(conn, "PRIVMSG "+target+" :"+msg);
+ }
+
+ function irc_join_channel(conn, chnl)
+ {
+ irc_sendline(conn, "JOIN "+chnl);
+ irc_ui_ensure_pane(chnl);
+ }
+
+ function handle_input_command(conn, arg, params)
+ {
+ arg = arg.toLowerCase();
+ if (arg == "j" || arg == "join") {
+ for (var idx = 0 ; idx < params.length; idx ++) {
+ irc_join_channel(conn, params[idx]);
+ }
+ return;
+ }
+
+ if(arg == "lp" || arg == "listpanes") {
+ irc_ui_println("--- begin list of panes", irc.target);
+ for (var k in irc.panes) {
+ if(k == irc.target) {
+ irc_ui_println("(active) : "+k, irc.target);
+ } else {
+ irc_ui_println(" : "+k, irc.target);
+ }
+ }
+ irc_ui_println("--- end list of panes", irc.target);
+ return;
+ }
+
+
+ if(arg == "m" || arg == "msg") {
+ irc_privmsg(conn, params[0], params.slice(1).join(" "));
+ return;
+ }
+
+ if(arg == "n" || arg == "nick") {
+ irc_sendline(conn, "NICK "+params[0]);
+ irc.nick = params[0];
+ return;
+ }
+ if(arg == "r" || arg == "raw") {
+ irc_sendline(conn, params.join(" "));
+ return;
+ }
+ if(arg == "q" || arg == "quit") {
+ irc_sendline(conn, "QUIT");
+ return;
+ }
+ if(arg == "w" || arg == "window") {
+ irc.target = params[0];
+ irc_ui_show_pane(irc.target);
+ return;
+ }
+ }
+
+ function handle_input_line(conn, line)
+ {
+ if(line[0] == "/") {
+ var parts = line.split(" ");
+ handle_input_command(conn, parts[0].slice(1), parts.slice(1));
+ } else {
+ irc_privmsg(conn, irc.target, line);
+ }
+ }
+
+ function irc_connected(conn, url)
+ {
+ console.log("connected to irc");
+ irc_ui_show_pane(" ");
+ irc_ui_println("connecting to "+url+"...");
+ // send user command
+ irc_sendline(conn, "NICK "+irc.nick);
+ irc_sendline(conn, "USER "+irc.nick+" "+irc.nick+" "+irc.nick+" :"+irc.nick);
+ irc.pinger = setInterval(function(){
+ if(irc.connected) {
+ irc_sendline(conn, "PING :i-hate-tcp-lol-"+new Date().getTime());
+ }
+ }, 10000);
+ input.addEventListener("keypress", function(ev){
+ // handle enter key
+ switch(ev.key) {
+ case "Enter":
+ handle_input_line(conn, input.value);
+ ev.preventDefault();
+ input.value = "";
+ }
+ return;
+ });
+ }
+
+ var e = document.getElementById("ws-server");
+ var ws = new WebSocket(e.value);
+ e = document.getElementById("irc-server");
+ var irc_url = e.value;
+
+ ws.onclose = function(err) {
+ console.log("connection closed "+err);
+ irc.connected = 0;
+ clearInterval(irc.pinger);
+ irc_ui_println("connection closed");
+ }
+
+ ws.onopen = function(ev) {
+ console.log("connect to "+irc_url);
+ irc_ui_println("resolving "+irc_url+" ...");
+ ws.send(irc_url);
+ }
+ ws.onmessage = function(ev) {
+ var data = ev.data;
+ if(irc.connected) {
+ irc_data(ws, data);
+ } else {
+ var j = JSON.parse(data);
+ if(j.error) {
+ console.log("WebSOCKS error: "+j.error);
+ console.log("try again");
+ setTimeout(function() {
+ ws.send(irc_url);
+ }, 1000);
+ }
+ irc.connected = j.success == "1";
+ if(irc.connected) {
+ irc_connected(ws, irc_url);
+ }
+ }
+ }
+
+
+}