From 9eafc53c91eb9b3972cb0cd938991a1fec3f92b4 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 3 Jun 2013 23:56:06 -0400 Subject: [PATCH 01/18] start experimenting with a fallback layer --- channel.js | 2 + notwebsocket.js | 118 ++++++++++++++++++++++++++++++++++ server.js | 48 ++++++++++++++ www/assets/js/callbacks.js | 7 ++ www/assets/js/notwebsocket.js | 50 ++++++++++++++ www/channel.html | 1 + 6 files changed, 226 insertions(+) create mode 100644 notwebsocket.js create mode 100644 www/assets/js/notwebsocket.js diff --git a/channel.js b/channel.js index 9aad63a6..af565ed4 100644 --- a/channel.js +++ b/channel.js @@ -19,6 +19,7 @@ var Logger = require("./logger.js"); var InfoGetter = require("./get-info.js"); var Server = require("./server.js"); var io = Server.io; +var NWS = require("./notwebsocket"); var Rank = require("./rank.js"); var Auth = require("./auth.js"); var ChatCommand = require("./chatcommand.js"); @@ -760,6 +761,7 @@ Channel.prototype.sendRecentChat = function(user) { Channel.prototype.sendAll = function(message, data) { io.sockets.in(this.name).emit(message, data); + NWS.inRoom(this.name).emit(message, data); } Channel.prototype.broadcastPlaylistMeta = function() { diff --git a/notwebsocket.js b/notwebsocket.js new file mode 100644 index 00000000..8cee92f0 --- /dev/null +++ b/notwebsocket.js @@ -0,0 +1,118 @@ +const chars = "abcdefghijklmnopqsrtuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "0123456789"; + + +var NotWebsocket = function() { + this.hash = ""; + for(var i = 0; i < 30; i++) { + this.hash += chars[parseInt(Math.random() * (chars.length - 1))]; + } + + this.pktqueue = []; + this.handlers = {}; + this.room = ""; +} + +NotWebsocket.prototype.emit = function(msg, data) { + //hack because something fishy is going on + if(typeof msg === "object") { + data = msg["1"]; + msg = msg["0"]; + } + var pkt = [msg, data]; + this.pktqueue.push(pkt); +} + +NotWebsocket.prototype.poll = function() { + var q = this.pktqueue; + this.pktqueue = []; + return q; +} + +NotWebsocket.prototype.on = function(msg, callback) { + if(!(msg in this.handlers)) + this.handlers[msg] = []; + this.handlers[msg].push(callback); +} + +NotWebsocket.prototype.recv = function(urlstr) { + var msg, data; + try { + var js = JSON.parse(urlstr); + msg = js[0]; + data = js[1]; + } + catch(e) { + console.log("Failed to parse NWS string"); + } + if(!msg) + return; + if(!(msg in this.handlers)) + return; + for(var i = 0; i < this.handlers[msg].length; i++) { + this.handlers[msg][i](data); + } +} + +NotWebsocket.prototype.join = function(rm) { + this.room = rm; +} + +NotWebsocket.prototype.disconnect = function() { + +} + +function sendJSON(res, obj) { + var response = JSON.stringify(obj, null, 4); + if(res.callback) { + response = res.callback + "(" + response + ")"; + } + var len = unescape(encodeURIComponent(response)).length; + + res.setHeader("Content-Type", "application/json"); + res.setHeader("Content-Length", len); + res.end(response); +} + +var clients = {}; +function newConnection(req, res) { + var nws = new NotWebsocket(); + clients[nws.hash] = nws; + sendJSON(res, nws.hash); + return nws; +} +exports.newConnection = newConnection; + +function msgReceived(req, res) { + var h = req.params.hash; + if(h in clients) { + if(req.params.str == "poll") { + sendJSON(res, clients[h].poll()); + } + else { + clients[h].recv(unescape(req.params.str)); + sendJSON(res, ""); + } + } +} +exports.msgReceived = msgReceived; + +function inRoom(rm) { + var cl = []; + for(var h in clients) { + if(clients[h].room == rm) { + cl.push(clients[h]); + } + } + + return { + emit: function() { + for(var i = 0; i < this.cl.length; i++) { + this.cl[i].emit(arguments); + } + }, + cl: cl + }; +} +exports.inRoom = inRoom; diff --git a/server.js b/server.js index 1676ad3a..5362237a 100644 --- a/server.js +++ b/server.js @@ -18,6 +18,7 @@ Logger.syslog.log("Starting CyTube v" + VERSION); var Config = require("./config.js"); var express = require("express"); var API = require("./api.js"); +var NWS = require("./notwebsocket"); var app = express(); app.get("/r/:channel(*)", function(req, res, next) { @@ -34,6 +35,53 @@ app.get("/api/:apireq(*)", function(req, res, next) { API.handle(req.url.substring(5), req, res); }); +function getClientIP(req) { + var ip; + var forward = req.header("x-forwarded-for"); + if(forward) { + ip = forward.split(",")[0]; + } + if(!ip) { + ip = req.connection.remoteAddress; + } + return ip; +} + +app.get("/nws/connect", function(req, res, next) { + var socket = NWS.newConnection(req, res); + var ip = getClientIP(req); + if(Database.checkGlobalBan(ip)) { + Logger.syslog.log("Disconnecting " + ip + " - bant"); + socket.emit("kick", { + reason: "You're globally banned!" + }); + socket.disconnect(true); + return; + } + socket.on("disconnect", function() { + exports.clients[ip]--; + }); + if(!(ip in exports.clients)) { + exports.clients[ip] = 1; + } + else { + exports.clients[ip]++; + } + if(exports.clients[ip] > Config.MAX_PER_IP) { + socket.emit("kick", { + reason: "Too many connections from your IP address" + }); + socket.disconnect(true); + return; + } + var user = new User(socket, ip); + Logger.syslog.log("Accepted connection from /" + user.ip); +}); + +app.get("/nws/:hash/:str", function(req, res, next) { + NWS.msgReceived(req, res); +}); + app.get("/:thing(*)", function(req, res, next) { res.sendfile(__dirname + "/www/" + req.params.thing); }); diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 2c2201b5..b2f36ec1 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -835,6 +835,7 @@ Callbacks = { } } +/* $.getScript(IO_URL+"/socket.io/socket.io.js", function() { try { socket = io.connect(IO_URL); @@ -846,3 +847,9 @@ $.getScript(IO_URL+"/socket.io/socket.io.js", function() { Callbacks.disconnect(); } }); +*/ + +socket = new NotWebsocket(); +for(var key in Callbacks) { + socket.on(key, Callbacks[key]); +} diff --git a/www/assets/js/notwebsocket.js b/www/assets/js/notwebsocket.js new file mode 100644 index 00000000..2add0ade --- /dev/null +++ b/www/assets/js/notwebsocket.js @@ -0,0 +1,50 @@ +var NotWebsocket = function() { + this.connected = false; + $.getJSON(WEB_URL + "/nws/connect", function(data) { + this.hash = data; + this.connected = true; + this.recv(["connect", undefined]); + this.pollint = setInterval(function() { + this.poll(); + }.bind(this), 100); + }.bind(this)); + + this.handlers = {}; +} + +NotWebsocket.prototype.emit = function(msg, data) { + if(!this.connected) { + setTimeout(function() { + this.emit(msg, data); + }.bind(this), 100); + } + var pkt = [msg, data]; + var str = escape(JSON.stringify(pkt)); + $.getJSON(WEB_URL+"/nws/"+this.hash+"/"+str, function(){}); +} + +NotWebsocket.prototype.on = function(msg, callback) { + if(!(msg in this.handlers)) + this.handlers[msg] = []; + this.handlers[msg].push(callback); +} + +NotWebsocket.prototype.poll = function() { + if(!this.connected) + return; + $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll", function(data) { + for(var i = 0; i < data.length; i++) { + console.log("DBG", data[i]); + this.recv(data[i]); + } + }.bind(this)); +} + +NotWebsocket.prototype.recv = function(pkt) { + var msg = pkt[0], data = pkt[1]; + if(!(msg in this.handlers)) + return; + for(var i = 0; i < this.handlers[msg].length; i++) { + this.handlers[msg][i](data); + } +} diff --git a/www/channel.html b/www/channel.html index a1977ff2..c3413473 100644 --- a/www/channel.html +++ b/www/channel.html @@ -313,6 +313,7 @@ + From 44fa360c600d3772cbd2a7ca45e11e1abbe0ea79 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Tue, 4 Jun 2013 11:46:06 -0400 Subject: [PATCH 02/18] Continue working on NWS --- notwebsocket.js | 84 ++++++++++++++++++++++++++--------- www/assets/js/notwebsocket.js | 48 +++++++++++++++++--- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/notwebsocket.js b/notwebsocket.js index 8cee92f0..ca2c72bb 100644 --- a/notwebsocket.js +++ b/notwebsocket.js @@ -1,8 +1,9 @@ +var Logger = require("./logger"); + const chars = "abcdefghijklmnopqsrtuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; - var NotWebsocket = function() { this.hash = ""; for(var i = 0; i < 30; i++) { @@ -12,21 +13,23 @@ var NotWebsocket = function() { this.pktqueue = []; this.handlers = {}; this.room = ""; + this.lastpoll = Date.now(); } NotWebsocket.prototype.emit = function(msg, data) { - //hack because something fishy is going on - if(typeof msg === "object") { - data = msg["1"]; - msg = msg["0"]; - } var pkt = [msg, data]; this.pktqueue.push(pkt); } NotWebsocket.prototype.poll = function() { - var q = this.pktqueue; - this.pktqueue = []; + this.lastpoll = Date.now(); + var q = []; + for(var i = 0; i < this.pktqueue.length; i++) { + q.push(this.pktqueue[i]); + } + this.pktqueue.length = 0; + if(q.length > 0) + console.log("sending", q.length); return q; } @@ -44,7 +47,8 @@ NotWebsocket.prototype.recv = function(urlstr) { data = js[1]; } catch(e) { - console.log("Failed to parse NWS string"); + Logger.errlog.log("Failed to parse NWS string"); + Logger.errlog.log(urlstr); } if(!msg) return; @@ -56,11 +60,32 @@ NotWebsocket.prototype.recv = function(urlstr) { } NotWebsocket.prototype.join = function(rm) { - this.room = rm; + if(!(rm in rooms)) { + rooms[rm] = []; + } + + rooms[rm].push(this); +} + +NotWebsocket.prototype.leave = function(rm) { + if(rm in rooms) { + var idx = rooms[rm].indexOf(this); + if(idx >= 0) { + rooms[rm].splice(idx, 1); + } + } } NotWebsocket.prototype.disconnect = function() { - + for(var rm in rooms) { + this.leave(rm); + } + + this.recv(JSON.stringify(["disconnect", undefined])); + this.emit("disconnect"); + + clients[this.hash] = null; + delete clients[this.hash]; } function sendJSON(res, obj) { @@ -76,6 +101,8 @@ function sendJSON(res, obj) { } var clients = {}; +var rooms = {}; + function newConnection(req, res) { var nws = new NotWebsocket(); clients[nws.hash] = nws; @@ -86,7 +113,7 @@ exports.newConnection = newConnection; function msgReceived(req, res) { var h = req.params.hash; - if(h in clients) { + if(h in clients && clients[h] != null) { if(req.params.str == "poll") { sendJSON(res, clients[h].poll()); } @@ -95,24 +122,37 @@ function msgReceived(req, res) { sendJSON(res, ""); } } + else { + res.send(404); + } } exports.msgReceived = msgReceived; function inRoom(rm) { var cl = []; - for(var h in clients) { - if(clients[h].room == rm) { - cl.push(clients[h]); + + if(rm in rooms) { + for(var i = 0; i < rooms[rm].length; i++) { + cl.push(rooms[rm][i]); } } - return { - emit: function() { - for(var i = 0; i < this.cl.length; i++) { - this.cl[i].emit(arguments); - } - }, - cl: cl + cl.emit = function(msg, data) { + for(var i = 0; i < this.length; i++) { + this[i].emit(msg, data); + } }; + + return cl; } exports.inRoom = inRoom; + +function checkDeadSockets() { + for(var h in clients) { + if(Date.now() - clients[h].lastpoll >= 2000) { + clients[h].disconnect(); + } + } +} + +setInterval(checkDeadSockets, 2000); diff --git a/www/assets/js/notwebsocket.js b/www/assets/js/notwebsocket.js index 2add0ade..59d3ae1e 100644 --- a/www/assets/js/notwebsocket.js +++ b/www/assets/js/notwebsocket.js @@ -1,5 +1,19 @@ var NotWebsocket = function() { this.connected = false; + $.getJSON(WEB_URL + "/nws/connect", function(data) { + console.log(data); + this.hash = data; + this.connected = true; + this.recv(["connect", undefined]); + this.pollint = setInterval(function() { + this.poll(); + }.bind(this), 500); + }.bind(this)); + + this.handlers = {}; +} + +NotWebsocket.prototype.reconnect = function() { $.getJSON(WEB_URL + "/nws/connect", function(data) { this.hash = data; this.connected = true; @@ -7,9 +21,14 @@ var NotWebsocket = function() { this.pollint = setInterval(function() { this.poll(); }.bind(this), 100); + }.bind(this)) + .fail(function() { + if(this.reconndelay < 10000) + this.reconndelay += 100; + setTimeout(function() { + this.reconnect(); + }.bind(this), this.reconndelay); }.bind(this)); - - this.handlers = {}; } NotWebsocket.prototype.emit = function(msg, data) { @@ -17,9 +36,10 @@ NotWebsocket.prototype.emit = function(msg, data) { setTimeout(function() { this.emit(msg, data); }.bind(this), 100); + return; } var pkt = [msg, data]; - var str = escape(JSON.stringify(pkt)); + var str = escape(JSON.stringify(pkt)).replace(/\//g, "%2F"); $.getJSON(WEB_URL+"/nws/"+this.hash+"/"+str, function(){}); } @@ -33,18 +53,34 @@ NotWebsocket.prototype.poll = function() { if(!this.connected) return; $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll", function(data) { + if(data.length > 0) + console.log("receiving", data.length); for(var i = 0; i < data.length; i++) { - console.log("DBG", data[i]); - this.recv(data[i]); + try { + this.recv(data[i]); + } + catch(e) { } } + }.bind(this)) + .fail(function() { + this.disconnect(); }.bind(this)); } NotWebsocket.prototype.recv = function(pkt) { var msg = pkt[0], data = pkt[1]; - if(!(msg in this.handlers)) + if(!(msg in this.handlers)) { return; + } for(var i = 0; i < this.handlers[msg].length; i++) { this.handlers[msg][i](data); } } + +NotWebsocket.prototype.disconnect = function() { + this.recv(["disconnect", undefined]); + clearInterval(this.pollint); + this.connected = false; + this.reconndelay = 100; + this.reconnect(); +} From cc1b378c3d1f7a230c33607eeaa7a48f8a2a0798 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Tue, 4 Jun 2013 12:11:16 -0400 Subject: [PATCH 03/18] NWS fixes --- notwebsocket.js | 9 +++++---- www/assets/js/notwebsocket.js | 17 +++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/notwebsocket.js b/notwebsocket.js index ca2c72bb..f1b65f91 100644 --- a/notwebsocket.js +++ b/notwebsocket.js @@ -28,8 +28,6 @@ NotWebsocket.prototype.poll = function() { q.push(this.pktqueue[i]); } this.pktqueue.length = 0; - if(q.length > 0) - console.log("sending", q.length); return q; } @@ -106,6 +104,7 @@ var rooms = {}; function newConnection(req, res) { var nws = new NotWebsocket(); clients[nws.hash] = nws; + res.callback = req.query.callback; sendJSON(res, nws.hash); return nws; } @@ -114,11 +113,13 @@ exports.newConnection = newConnection; function msgReceived(req, res) { var h = req.params.hash; if(h in clients && clients[h] != null) { - if(req.params.str == "poll") { + var str = req.params.str; + res.callback = req.query.callback; + if(str == "poll") { sendJSON(res, clients[h].poll()); } else { - clients[h].recv(unescape(req.params.str)); + clients[h].recv(unescape(str)); sendJSON(res, ""); } } diff --git a/www/assets/js/notwebsocket.js b/www/assets/js/notwebsocket.js index 59d3ae1e..67d6765a 100644 --- a/www/assets/js/notwebsocket.js +++ b/www/assets/js/notwebsocket.js @@ -7,7 +7,7 @@ var NotWebsocket = function() { this.recv(["connect", undefined]); this.pollint = setInterval(function() { this.poll(); - }.bind(this), 500); + }.bind(this), 100); }.bind(this)); this.handlers = {}; @@ -52,17 +52,20 @@ NotWebsocket.prototype.on = function(msg, callback) { NotWebsocket.prototype.poll = function() { if(!this.connected) return; - $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll", function(data) { - if(data.length > 0) - console.log("receiving", data.length); + if(this.polling) + return false; + $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll?callback=?", function(data) { + this.polling = true; for(var i = 0; i < data.length; i++) { try { this.recv(data[i]); } catch(e) { } } + this.polling = false; }.bind(this)) .fail(function() { + console.log(arguments); this.disconnect(); }.bind(this)); } @@ -81,6 +84,8 @@ NotWebsocket.prototype.disconnect = function() { this.recv(["disconnect", undefined]); clearInterval(this.pollint); this.connected = false; - this.reconndelay = 100; - this.reconnect(); + this.reconndelay = 1000; + setTimeout(function() { + this.reconnect(); + }.bind(this), this.reconndelay); } From ddc1c56986a05dd5a4d324f39e761ff978140406 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Tue, 4 Jun 2013 15:28:54 -0400 Subject: [PATCH 04/18] Adaptive poll rate for NWS --- www/assets/js/notwebsocket.js | 106 ++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/www/assets/js/notwebsocket.js b/www/assets/js/notwebsocket.js index 67d6765a..fcdf7677 100644 --- a/www/assets/js/notwebsocket.js +++ b/www/assets/js/notwebsocket.js @@ -1,18 +1,79 @@ var NotWebsocket = function() { this.connected = false; + this.polltmr = false; $.getJSON(WEB_URL + "/nws/connect", function(data) { console.log(data); this.hash = data; this.connected = true; this.recv(["connect", undefined]); - this.pollint = setInterval(function() { - this.poll(); - }.bind(this), 100); + this.pollint = 100; + this.pollonce(); }.bind(this)); this.handlers = {}; } +NotWebsocket.prototype.pollonce = function() { + if(this.polltmr !== false) + clearTimeout(this.polltmr); + if(!this.connected) + return; + this.poll(); + this.polltmr = setTimeout(function() { + this.pollonce(); + }.bind(this), this.pollint); +} + +NotWebsocket.prototype.poll = function() { + if(!this.connected) + return; + if(this.polling) + return; + $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll?callback=?", function(data) { + this.polling = true; + // Adaptive polling rate + // Poll every 1000ms if no activity + // every 500ms if minor activity + // every 100ms is very active + if(data.length == 0) { + this.pollint = 1000; + } + else if(data.length < 10 && this.pollint < 500) { + this.pollint += 100; + } + else if(data.length > 10) { + this.pollint = 100; + } + for(var i = 0; i < data.length; i++) { + try { + this.recv(data[i]); + } + catch(e) { } + } + this.polling = false; + }.bind(this)) + .fail(function() { + this.disconnect(); + }.bind(this)); +} + +NotWebsocket.prototype.emit = function(msg, data) { + if(!this.connected) { + setTimeout(function() { + this.emit(msg, data); + }.bind(this), 100); + return; + } + var pkt = [msg, data]; + var str = escape(JSON.stringify(pkt)).replace(/\//g, "%2F"); + $.getJSON(WEB_URL+"/nws/"+this.hash+"/"+str, function() { + // Poll more quickly because sending a packet usually means + // expecting some data to come back + this.pollint = 100; + this.pollonce(); + }.bind(this)); +} + NotWebsocket.prototype.reconnect = function() { $.getJSON(WEB_URL + "/nws/connect", function(data) { this.hash = data; @@ -24,52 +85,19 @@ NotWebsocket.prototype.reconnect = function() { }.bind(this)) .fail(function() { if(this.reconndelay < 10000) - this.reconndelay += 100; + this.reconndelay += 500; setTimeout(function() { this.reconnect(); }.bind(this), this.reconndelay); }.bind(this)); } -NotWebsocket.prototype.emit = function(msg, data) { - if(!this.connected) { - setTimeout(function() { - this.emit(msg, data); - }.bind(this), 100); - return; - } - var pkt = [msg, data]; - var str = escape(JSON.stringify(pkt)).replace(/\//g, "%2F"); - $.getJSON(WEB_URL+"/nws/"+this.hash+"/"+str, function(){}); -} - NotWebsocket.prototype.on = function(msg, callback) { if(!(msg in this.handlers)) this.handlers[msg] = []; this.handlers[msg].push(callback); } -NotWebsocket.prototype.poll = function() { - if(!this.connected) - return; - if(this.polling) - return false; - $.getJSON(WEB_URL+"/nws/"+this.hash+"/poll?callback=?", function(data) { - this.polling = true; - for(var i = 0; i < data.length; i++) { - try { - this.recv(data[i]); - } - catch(e) { } - } - this.polling = false; - }.bind(this)) - .fail(function() { - console.log(arguments); - this.disconnect(); - }.bind(this)); -} - NotWebsocket.prototype.recv = function(pkt) { var msg = pkt[0], data = pkt[1]; if(!(msg in this.handlers)) { @@ -82,7 +110,9 @@ NotWebsocket.prototype.recv = function(pkt) { NotWebsocket.prototype.disconnect = function() { this.recv(["disconnect", undefined]); - clearInterval(this.pollint); + if(this.polltmr !== false) + clearTimeout(this.polltmr); + this.polltmr = false; this.connected = false; this.reconndelay = 1000; setTimeout(function() { From cfc866400cefd0f2f84919dba8ff76a66a7fbdee Mon Sep 17 00:00:00 2001 From: calzoneman Date: Tue, 4 Jun 2013 15:41:34 -0400 Subject: [PATCH 05/18] Break out NWS into a user option --- www/assets/js/callbacks.js | 7 ------- www/assets/js/client.js | 3 ++- www/assets/js/functions.js | 28 ++++++++++++++++++++++++++++ www/assets/js/notwebsocket.js | 1 - 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index b2f36ec1..2c2201b5 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -835,7 +835,6 @@ Callbacks = { } } -/* $.getScript(IO_URL+"/socket.io/socket.io.js", function() { try { socket = io.connect(IO_URL); @@ -847,9 +846,3 @@ $.getScript(IO_URL+"/socket.io/socket.io.js", function() { Callbacks.disconnect(); } }); -*/ - -socket = new NotWebsocket(); -for(var key in Callbacks) { - socket.on(key, Callbacks[key]); -} diff --git a/www/assets/js/client.js b/www/assets/js/client.js index d44cc27c..dcf7af82 100644 --- a/www/assets/js/client.js +++ b/www/assets/js/client.js @@ -71,7 +71,8 @@ var USEROPTS = { modhat : parseBool(getOrDefault("cytube_modhat", false)), blink_title : parseBool(getOrDefault("cytube_blink_title", false)), sync_accuracy : parseFloat(getOrDefault("cytube_sync_accuracy", 2)) || 2, - chatbtn : parseBool(getOrDefault("cytube_chatbtn", false)) + chatbtn : parseBool(getOrDefault("cytube_chatbtn", false)), + altsocket : parseBool(getOrDefault("cytube_altsocket", false)) }; applyOpts(); $("#optlink").click(showUserOpts); diff --git a/www/assets/js/functions.js b/www/assets/js/functions.js index 47890980..7b41dd17 100644 --- a/www/assets/js/functions.js +++ b/www/assets/js/functions.js @@ -1070,6 +1070,13 @@ function showUserOpts() { sendbtn.prop("checked", USEROPTS.chatbtn); addOption("Send Button", sendbtncontainer); + var altsocketcontainer = $("