2015-10-24 15:59:58 -07:00
|
|
|
var FFZ = window.FrankerFaceZ,
|
|
|
|
constants = require('./constants');
|
2015-01-20 01:53:18 -05:00
|
|
|
|
|
|
|
FFZ.prototype._ws_open = false;
|
|
|
|
FFZ.prototype._ws_delay = 0;
|
2015-11-01 17:28:19 -05:00
|
|
|
FFZ.prototype._ws_host_idx = -1;
|
|
|
|
FFZ.prototype._ws_current_pool = -1;
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
|
|
|
|
FFZ.ws_commands = {};
|
2015-02-08 02:01:09 -05:00
|
|
|
FFZ.ws_on_close = [];
|
2015-01-20 01:53:18 -05:00
|
|
|
|
|
|
|
|
|
|
|
// ----------------
|
2015-11-01 17:28:19 -05:00
|
|
|
// Settings
|
2015-01-20 01:53:18 -05:00
|
|
|
// ----------------
|
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
var ffz_socket_seed;
|
|
|
|
|
|
|
|
try {
|
|
|
|
ffz_socket_seed = JSON.parse(localStorage.ffz_socket_seed);
|
|
|
|
} catch(err) { }
|
|
|
|
|
|
|
|
if ( ! ffz_socket_seed ) {
|
|
|
|
ffz_socket_seed = Math.random();
|
|
|
|
localStorage.ffz_socket_seed = JSON.stringify(ffz_socket_seed);
|
2015-08-02 02:41:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
FFZ.settings_info.socket_server_pool = {
|
|
|
|
type: "select",
|
|
|
|
options: {
|
|
|
|
0: "Disabled",
|
|
|
|
1: "Production",
|
|
|
|
2: "Development"
|
|
|
|
},
|
|
|
|
|
|
|
|
value: ffz_socket_seed > 0.65 ? 1 : 0,
|
|
|
|
|
|
|
|
process_value: function(val) {
|
|
|
|
if ( typeof val === "string" )
|
|
|
|
return parseInt(val) || 0;
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
|
|
|
|
visible: function() { return (localStorage.hasOwnProperty('ffz_socket_server_pool') && this.settings.socket_server_pool !== 1) || this.settings.developer_mode || (Date.now() - parseInt(localStorage.ffzLastDevMode || "0")) < 604800000; },
|
|
|
|
|
|
|
|
category: "Debugging",
|
|
|
|
name: "Socket Server Cluster",
|
|
|
|
help: "Select which cluster of socket servers to connect to.",
|
|
|
|
|
|
|
|
on_update: function(val) {
|
|
|
|
if ( val === this._ws_current_pool )
|
|
|
|
return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
this._ws_sock.close();
|
|
|
|
} catch(err) { }
|
|
|
|
|
|
|
|
this._ws_open = false;
|
|
|
|
this._ws_delay = 0;
|
|
|
|
this._ws_host_idx = -1;
|
|
|
|
|
|
|
|
if ( this._ws_recreate_timer ) {
|
|
|
|
clearTimeout(this._ws_recreate_timer);
|
|
|
|
this._ws_recreate_timer = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( val === 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.ws_create();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------
|
|
|
|
// Socket Creation
|
|
|
|
// ----------------
|
2015-10-24 22:44:00 -04:00
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
FFZ.prototype.ws_create = function() {
|
2015-01-27 17:05:51 -05:00
|
|
|
var f = this, ws;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
this._ws_last_req = 1;
|
|
|
|
this._ws_callbacks = {1: f._ws_on_hello.bind(f)};
|
2015-01-20 01:53:18 -05:00
|
|
|
this._ws_pending = this._ws_pending || [];
|
2015-11-01 17:28:19 -05:00
|
|
|
this._ws_recreate_timer = null;
|
|
|
|
|
|
|
|
var pool_id = this.settings.socket_server_pool,
|
|
|
|
pool = constants.WS_SERVER_POOLS[pool_id];
|
|
|
|
|
|
|
|
this._ws_current_pool = pool_id;
|
|
|
|
|
|
|
|
if ( ! pool )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( this._ws_host_idx < 0 )
|
|
|
|
this._ws_host_idx = Math.floor(Math.random() * pool.length);
|
|
|
|
|
|
|
|
var server = pool[this._ws_host_idx];
|
|
|
|
|
|
|
|
this.log("Using Socket Server: " + server + " [" + pool_id + ":" + this._ws_host_idx + "]");
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-01-27 17:05:51 -05:00
|
|
|
try {
|
2015-11-01 17:28:19 -05:00
|
|
|
ws = this._ws_sock = new WebSocket(pool[this._ws_host_idx]);
|
2015-01-27 17:05:51 -05:00
|
|
|
} catch(err) {
|
|
|
|
this._ws_exists = false;
|
|
|
|
return this.log("Error Creating WebSocket: " + err);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._ws_exists = true;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
|
|
|
ws.onopen = function(e) {
|
|
|
|
f._ws_open = true;
|
|
|
|
f._ws_delay = 0;
|
2015-11-01 17:28:19 -05:00
|
|
|
f.log("Socket Connected.");
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
// Hard-code the first command.
|
|
|
|
ws.send("1 hello " + JSON.stringify(["ffz_" + FFZ.version_info, localStorage.ffzClientId]));
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
var user = f.get_user();
|
|
|
|
if ( user )
|
|
|
|
f.ws_send("setuser", user.login);
|
|
|
|
|
2015-05-17 19:02:57 -04:00
|
|
|
// Join the right channel if we're in the dashboard.
|
|
|
|
if ( f.is_dashboard ) {
|
|
|
|
var match = location.pathname.match(/\/([^\/]+)/);
|
2015-07-04 17:06:36 -04:00
|
|
|
if ( match ) {
|
2015-10-26 12:13:28 -07:00
|
|
|
f.ws_send("sub", "channel." + match[1]);
|
2015-07-04 17:06:36 -04:00
|
|
|
}
|
2015-05-17 19:02:57 -04:00
|
|
|
}
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
// Send the current rooms.
|
2015-06-10 18:46:04 -04:00
|
|
|
for(var room_id in f.rooms) {
|
|
|
|
if ( ! f.rooms.hasOwnProperty(room_id) || ! f.rooms[room_id] )
|
|
|
|
continue;
|
|
|
|
|
2015-10-26 12:13:28 -07:00
|
|
|
f.ws_send("sub", "room." + room_id);
|
2015-06-10 18:46:04 -04:00
|
|
|
|
|
|
|
if ( f.rooms[room_id].needs_history ) {
|
|
|
|
f.rooms[room_id].needs_history = false;
|
|
|
|
if ( ! f.has_bttv && f.settings.chat_history )
|
|
|
|
f.ws_send("chat_history", [room_id,25], f._load_history.bind(f, room_id));
|
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-07-04 17:06:36 -04:00
|
|
|
// Send the channel(s).
|
|
|
|
if ( f._cindex ) {
|
|
|
|
var channel_id = f._cindex.get('controller.id'),
|
|
|
|
hosted_id = f._cindex.get('controller.hostModeTarget.id');
|
|
|
|
|
|
|
|
if ( channel_id )
|
2015-10-26 12:13:28 -07:00
|
|
|
f.ws_send("sub", "channel." + channel_id);
|
2015-07-04 17:06:36 -04:00
|
|
|
|
|
|
|
if ( hosted_id )
|
2015-10-26 12:13:28 -07:00
|
|
|
f.ws_send("sub", "channel." + hosted_id);
|
2015-07-04 17:06:36 -04:00
|
|
|
}
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
// Send any pending commands.
|
|
|
|
var pending = f._ws_pending;
|
|
|
|
f._ws_pending = [];
|
|
|
|
|
|
|
|
for(var i=0; i < pending.length; i++) {
|
|
|
|
var d = pending[i];
|
|
|
|
f.ws_send(d[0], d[1], d[2]);
|
|
|
|
}
|
2015-10-26 12:13:28 -07:00
|
|
|
|
|
|
|
// If reconnecting, get the backlog that we missed.
|
|
|
|
if ( f._ws_offline_time ) {
|
|
|
|
var timestamp = f._ws_offline_time;
|
|
|
|
delete f._ws_offline_time;
|
|
|
|
f.ws_send("ready", timestamp);
|
|
|
|
} else {
|
|
|
|
f.ws_send("ready", 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ws.onerror = function() {
|
|
|
|
if ( ! f._ws_offline_time ) {
|
|
|
|
f._ws_offline_time = new Date().getTime();
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ws.onclose = function(e) {
|
2015-11-01 17:28:19 -05:00
|
|
|
var was_open = f._ws_open;
|
2015-03-30 00:52:18 -04:00
|
|
|
f.log("Socket closed. (Code: " + e.code + ", Reason: " + e.reason + ")");
|
2015-11-01 17:28:19 -05:00
|
|
|
|
|
|
|
// If a recreate is already scheduled, this is expected.
|
|
|
|
if ( f._ws_recreate_timer )
|
|
|
|
return;
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
f._ws_open = false;
|
2015-10-26 12:13:28 -07:00
|
|
|
if ( ! f._ws_offline_time ) {
|
|
|
|
f._ws_offline_time = new Date().getTime();
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-02-08 02:01:09 -05:00
|
|
|
// When the connection closes, run our callbacks.
|
2015-10-24 16:04:27 -07:00
|
|
|
for (var i=0; i < FFZ.ws_on_close.length; i++) {
|
2015-02-08 02:01:09 -05:00
|
|
|
try {
|
|
|
|
FFZ.ws_on_close[i].bind(f)();
|
|
|
|
} catch(err) {
|
|
|
|
f.log("Error on Socket Close Callback: " + err);
|
|
|
|
}
|
|
|
|
}
|
2015-03-15 21:24:22 -04:00
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
// Cycle selected server if our last attempt to connect didn't
|
|
|
|
// actually connect.
|
|
|
|
if ( ! was_open )
|
|
|
|
// Actually, let's get a random new server instead.
|
|
|
|
f._ws_host_idx = -1; //(f._ws_host_idx + 1) % pool.length;
|
2015-08-02 02:41:03 -04:00
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
// We never ever want to not have a socket.
|
2015-02-24 00:33:29 -05:00
|
|
|
if ( f._ws_delay < 60000 )
|
2015-06-10 18:46:04 -04:00
|
|
|
f._ws_delay += (Math.floor(Math.random()*10) + 5) * 1000;
|
2015-02-24 00:33:29 -05:00
|
|
|
else
|
|
|
|
// Randomize delay.
|
|
|
|
f._ws_delay = (Math.floor(Math.random()*60)+30)*1000;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
f._ws_recreate_timer = setTimeout(f.ws_create.bind(f), f._ws_delay);
|
2015-01-20 01:53:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ws.onmessage = function(e) {
|
|
|
|
// Messages are formatted as REQUEST_ID SUCCESS/FUNCTION_NAME[ JSON_DATA]
|
|
|
|
var cmd, data, ind = e.data.indexOf(" "),
|
|
|
|
msg = e.data.substr(ind + 1),
|
|
|
|
request = parseInt(e.data.slice(0, ind));
|
|
|
|
|
|
|
|
ind = msg.indexOf(" ");
|
|
|
|
if ( ind === -1 )
|
|
|
|
ind = msg.length;
|
|
|
|
|
|
|
|
cmd = msg.slice(0, ind);
|
|
|
|
msg = msg.substr(ind + 1);
|
|
|
|
if ( msg )
|
|
|
|
data = JSON.parse(msg);
|
|
|
|
|
|
|
|
if ( request === -1 ) {
|
|
|
|
// It's a command from the server.
|
|
|
|
var command = FFZ.ws_commands[cmd];
|
|
|
|
if ( command )
|
|
|
|
command.bind(f)(data);
|
|
|
|
else
|
2015-03-30 00:52:18 -04:00
|
|
|
f.log("Invalid command: " + cmd, data, false, true);
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-10-24 21:33:31 -07:00
|
|
|
} else if ( cmd === "error" ) {
|
|
|
|
f.log("Socket server reported error: " + data);
|
|
|
|
if (f._ws_callbacks[request] )
|
|
|
|
delete f._ws_callbacks[request];
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
} else {
|
2015-11-01 17:28:19 -05:00
|
|
|
var success = cmd === 'ok',
|
2015-07-31 21:40:51 -04:00
|
|
|
has_callback = typeof f._ws_callbacks[request] === "function";
|
2015-02-26 00:42:11 -05:00
|
|
|
|
2015-06-10 18:46:04 -04:00
|
|
|
if ( ! has_callback )
|
2015-03-30 00:52:18 -04:00
|
|
|
f.log("Socket Reply to " + request + " - " + (success ? "SUCCESS" : "FAIL"), data, false, true);
|
2015-02-26 00:42:11 -05:00
|
|
|
|
2015-06-10 18:46:04 -04:00
|
|
|
else {
|
|
|
|
try {
|
|
|
|
f._ws_callbacks[request](success, data);
|
|
|
|
} catch(err) {
|
|
|
|
f.error("Callback for " + request + ": " + err);
|
|
|
|
}
|
2015-08-28 22:54:12 -04:00
|
|
|
|
2015-10-24 21:33:31 -07:00
|
|
|
delete f._ws_callbacks[request];
|
2015-01-20 01:53:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FFZ.prototype.ws_send = function(func, data, callback, can_wait) {
|
|
|
|
if ( ! this._ws_open ) {
|
|
|
|
if ( can_wait ) {
|
|
|
|
var pending = this._ws_pending = this._ws_pending || [];
|
|
|
|
pending.push([func, data, callback]);
|
|
|
|
return true;
|
|
|
|
} else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var request = ++this._ws_last_req;
|
|
|
|
data = data !== undefined ? " " + JSON.stringify(data) : "";
|
|
|
|
|
|
|
|
if ( callback )
|
|
|
|
this._ws_callbacks[request] = callback;
|
|
|
|
|
2015-07-13 21:52:44 -04:00
|
|
|
try {
|
|
|
|
this._ws_sock.send(request + " " + func + data);
|
|
|
|
} catch(err) {
|
|
|
|
this.log("Socket Send Error: " + err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
return request;
|
2015-02-24 00:33:29 -05:00
|
|
|
}
|
|
|
|
|
2015-06-05 03:59:28 -04:00
|
|
|
|
|
|
|
// ----------------
|
|
|
|
// HELLO Response
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
FFZ.prototype._ws_on_hello = function(success, data) {
|
|
|
|
if ( ! success )
|
|
|
|
return this.log("Error Saying Hello: " + data);
|
|
|
|
|
|
|
|
localStorage.ffzClientId = data;
|
|
|
|
this.log("Client ID: " + data);
|
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
/*var survey = {},
|
2015-06-05 03:59:28 -04:00
|
|
|
set = survey['settings'] = {};
|
|
|
|
|
|
|
|
for(var key in FFZ.settings_info)
|
|
|
|
set[key] = this.settings[key];
|
|
|
|
|
|
|
|
set["keywords"] = this.settings.keywords.length;
|
|
|
|
set["banned_words"] = this.settings.banned_words.length;
|
|
|
|
|
|
|
|
|
|
|
|
// Detect BTTV.
|
|
|
|
survey['bttv'] = this.has_bttv || !!document.head.querySelector('script[src*="betterttv"]');
|
|
|
|
|
|
|
|
|
|
|
|
// Client Info
|
|
|
|
survey['user-agent'] = navigator.userAgent;
|
|
|
|
survey['screen'] = [screen.width, screen.height];
|
|
|
|
survey['language'] = navigator.language;
|
|
|
|
survey['platform'] = navigator.platform;
|
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
this.ws_send("survey", [survey]);*/
|
2015-06-05 03:59:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-11-01 17:28:19 -05:00
|
|
|
// ----------------
|
|
|
|
// Reconnect Logic
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
FFZ.ws_commands.reconnect = function() {
|
|
|
|
this.log("Socket Reconnect Command Received");
|
|
|
|
|
|
|
|
// Set the socket as closed and close it.
|
|
|
|
this._ws_open = false;
|
|
|
|
this._ws_sock.close();
|
|
|
|
|
|
|
|
// Socket Close Callbacks
|
|
|
|
for(var i=0; i < FFZ.ws_on_close.length; i++) {
|
|
|
|
try {
|
|
|
|
FFZ.ws_on_close[i].bind(this)();
|
|
|
|
} catch(err) {
|
|
|
|
this.log("Error on Socket Close Callback: " + err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Randomize the reconnect delay to avoid a complete hammering.
|
|
|
|
this._ws_delay = Math.floor(Math.random() * 5) * 1000;
|
|
|
|
this._ws_recreate_timer = setTimeout(this.ws_create.bind(this), this._ws_delay);
|
|
|
|
}
|
|
|
|
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2015-02-24 00:33:29 -05:00
|
|
|
// ----------------
|
|
|
|
// Authorization
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
FFZ.ws_commands.do_authorize = function(data) {
|
|
|
|
// Try finding a channel we can send on.
|
|
|
|
var conn;
|
|
|
|
for(var room_id in this.rooms) {
|
|
|
|
if ( ! this.rooms.hasOwnProperty(room_id) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
var r = this.rooms[room_id];
|
|
|
|
if ( r && r.room && !r.room.get('roomProperties.eventchat') && !r.room.get('isGroupRoom') && r.room.tmiRoom ) {
|
|
|
|
var c = r.room.tmiRoom._getConnection();
|
|
|
|
if ( c.isConnected ) {
|
|
|
|
conn = c;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( conn )
|
|
|
|
conn._send("PRIVMSG #frankerfacezauthorizer :AUTH " + data);
|
|
|
|
else
|
|
|
|
// Try again shortly.
|
|
|
|
setTimeout(FFZ.ws_commands.do_authorize.bind(this, data), 5000);
|
2015-01-12 17:58:07 -05:00
|
|
|
}
|