rewrite socket client to hide implementation
parent
71a07976f4
commit
f127f842df
|
@ -197,6 +197,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
@if(isProd) {
|
||||
@jsTagCompiled("lichess.js")
|
||||
} else {
|
||||
@jsTag("util.js")
|
||||
@jsTag("socket.js")
|
||||
@jsTag("main.js")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
. bin/lilarc
|
||||
|
||||
./ui/build prod
|
||||
# ./ui/build prod
|
||||
|
||||
SRC=public/javascripts
|
||||
TARGET=public/compiled
|
||||
|
@ -17,7 +17,7 @@ for file in tv.js puzzle.js chart2.js user.js coordinate.js; do
|
|||
fi
|
||||
done
|
||||
|
||||
orig="$SRC/socket.js $SRC/main.js"
|
||||
orig="$SRC/util.js $SRC/socket.js $SRC/main.js"
|
||||
comp=$TARGET/lichess.js
|
||||
lilalog "Compiling lichess.js"
|
||||
closure --js $orig --js_output_file $comp
|
||||
|
|
|
@ -229,7 +229,7 @@ $(function() {
|
|||
values.server = v;
|
||||
updateAnswer();
|
||||
} else if (t === 'n') setTimeout(function() {
|
||||
var v = Math.round(lichess.socket.averageLag);
|
||||
var v = Math.round(lichess.socket.averageLag());
|
||||
charts.network.series[0].points[0].update(v);
|
||||
values.network = v;
|
||||
updateAnswer();
|
||||
|
|
|
@ -2,107 +2,6 @@
|
|||
// @compilation_level ADVANCED_OPTIMIZATIONS
|
||||
// ==/ClosureCompiler==
|
||||
|
||||
var lichess = window.lichess = window.lichess || {};
|
||||
|
||||
lichess.getParameterByName = function(name) {
|
||||
var match = RegExp('[?&]' + name + '=([^&]*)').exec(location.search);
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
||||
};
|
||||
|
||||
// declare now, populate later in a distinct script.
|
||||
var lichess_translations = lichess_translations || [];
|
||||
|
||||
function withStorage(f) {
|
||||
// can throw an exception when storage is full
|
||||
try {
|
||||
return !!window.localStorage ? f(window.localStorage) : null;
|
||||
} catch (e) {}
|
||||
}
|
||||
lichess.storage = {
|
||||
get: function(k) {
|
||||
return withStorage(function(s) {
|
||||
return s.getItem(k);
|
||||
});
|
||||
},
|
||||
remove: function(k) {
|
||||
withStorage(function(s) {
|
||||
s.removeItem(k);
|
||||
});
|
||||
},
|
||||
set: function(k, v) {
|
||||
// removing first may help http://stackoverflow.com/questions/2603682/is-anyone-else-receiving-a-quota-exceeded-err-on-their-ipad-when-accessing-local
|
||||
withStorage(function(s) {
|
||||
s.removeItem(k);
|
||||
s.setItem(k, v);
|
||||
});
|
||||
}
|
||||
};
|
||||
lichess.once = function(key, mod) {
|
||||
if (mod === 'always') return true;
|
||||
if (!lichess.storage.get(key)) {
|
||||
lichess.storage.set(key, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
lichess.trans = function(i18n) {
|
||||
return function(key) {
|
||||
var str = i18n[key] || key;
|
||||
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
|
||||
str = str.replace('%s', arg);
|
||||
});
|
||||
return str;
|
||||
};
|
||||
};
|
||||
lichess.widget = function(name, prototype) {
|
||||
var constructor = $[name] = function(options, element) {
|
||||
var self = this;
|
||||
self.element = $(element);
|
||||
$.data(element, name, self);
|
||||
self.options = options;
|
||||
self._create();
|
||||
};
|
||||
constructor.prototype = prototype;
|
||||
$.fn[name] = function(method) {
|
||||
var returnValue = this;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
if (typeof method === 'string') this.each(function() {
|
||||
var instance = $.data(this, name);
|
||||
if (!$.isFunction(instance[method]) || method.charAt(0) === "_")
|
||||
return $.error("no such method '" + method + "' for " + name + " widget instance");
|
||||
returnValue = instance[method].apply(instance, args);
|
||||
});
|
||||
else this.each(function() {
|
||||
if ($.data(this, name)) return $.error("widget " + name + " already bound to " + this);
|
||||
$.data(this, name, new constructor(method, this));
|
||||
});
|
||||
return returnValue;
|
||||
};
|
||||
};
|
||||
lichess.isTrident = navigator.userAgent.indexOf('Trident/') > -1;
|
||||
lichess.isChrome = navigator.userAgent.indexOf('Chrome/') > -1;
|
||||
lichess.isSafari = navigator.userAgent.indexOf('Safari/') > -1 && !lichess.isChrome;
|
||||
lichess.spinnerHtml = '<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>';
|
||||
lichess.assetUrl = function(url, noVersion) {
|
||||
return $('body').data('asset-url') + url + (noVersion ? '' : '?v=' + $('body').data('asset-version'));
|
||||
};
|
||||
lichess.loadCss = function(url) {
|
||||
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', lichess.assetUrl(url)));
|
||||
}
|
||||
lichess.loadScript = function(url, noVersion) {
|
||||
return $.ajax({
|
||||
dataType: "script",
|
||||
cache: true,
|
||||
url: lichess.assetUrl(url, noVersion)
|
||||
});
|
||||
};
|
||||
lichess.hopscotch = function(f) {
|
||||
lichess.loadCss('/assets/vendor/hopscotch/dist/css/hopscotch.min.css');
|
||||
lichess.loadScript("/assets/vendor/hopscotch/dist/js/hopscotch.min.js").done(f);
|
||||
}
|
||||
lichess.slider = function() {
|
||||
return lichess.loadScript('/assets/javascripts/vendor/jquery-ui.slider.min.js', true);
|
||||
};
|
||||
lichess.challengeApp = (function() {
|
||||
var instance;
|
||||
var $toggle = $('#challenge_notifications_tag');
|
||||
|
@ -136,55 +35,6 @@ lichess.challengeApp = (function() {
|
|||
};
|
||||
})();
|
||||
|
||||
lichess.isPageVisible = document.visibilityState !== 'hidden';
|
||||
lichess.notifications = [];
|
||||
// using document.hidden doesn't entirely work because it may return false if the window is not minimized but covered by other applications
|
||||
window.addEventListener('focus', function() {
|
||||
lichess.isPageVisible = true;
|
||||
lichess.notifications.forEach(function(n) {
|
||||
n.close();
|
||||
});
|
||||
lichess.notifications = [];
|
||||
});
|
||||
window.addEventListener('blur', function() {
|
||||
lichess.isPageVisible = false;
|
||||
});
|
||||
lichess.desktopNotification = function(msg) {
|
||||
var title = 'lichess.org';
|
||||
var icon = 'http://lichess1.org/assets/images/logo.256.png';
|
||||
var notify = function() {
|
||||
var notification = new Notification(title, {
|
||||
icon: icon,
|
||||
body: msg
|
||||
});
|
||||
notification.onclick = function() {
|
||||
window.focus();
|
||||
}
|
||||
lichess.notifications.push(notification);
|
||||
};
|
||||
if (lichess.isPageVisible || !('Notification' in window) || Notification.permission === 'denied') return;
|
||||
if (Notification.permission === 'granted') notify();
|
||||
else Notification.requestPermission(function(p) {
|
||||
if (p === 'granted') notify();
|
||||
});
|
||||
};
|
||||
lichess.unique = function(xs) {
|
||||
return xs.filter(function(x, i) {
|
||||
return xs.indexOf(x) === i;
|
||||
});
|
||||
};
|
||||
lichess.numberFormat = (function() {
|
||||
if (window.Intl && Intl.NumberFormat) {
|
||||
var formatter = new Intl.NumberFormat();
|
||||
return function(n) {
|
||||
return formatter.format(n);
|
||||
}
|
||||
}
|
||||
return function(n) {
|
||||
return n;
|
||||
};
|
||||
})();
|
||||
|
||||
(function() {
|
||||
|
||||
/////////////
|
||||
|
@ -600,9 +450,9 @@ lichess.numberFormat = (function() {
|
|||
|
||||
setTimeout(function() {
|
||||
if (lichess.socket === null) {
|
||||
lichess.socket = new lichess.StrongSocket("/socket", 0);
|
||||
lichess.socket = lichess.StrongSocket("/socket", 0);
|
||||
}
|
||||
$.idleTimer(lichess.idleTime, lichess.socket.destroy.bind(lichess.socket), lichess.socket.connect.bind(lichess.socket));
|
||||
$.idleTimer(lichess.idleTime, lichess.socket.destroy, lichess.socket.connect);
|
||||
}, 200);
|
||||
|
||||
// themepicker
|
||||
|
@ -1114,7 +964,7 @@ lichess.numberFormat = (function() {
|
|||
if (data.player.spectator && lichess.openInMobileApp(data.game.id)) return;
|
||||
var round;
|
||||
if (data.tournament) $('body').data('tournament-id', data.tournament.id);
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
lichess.socket = lichess.StrongSocket(
|
||||
data.url.socket,
|
||||
data.player.version, {
|
||||
options: {
|
||||
|
@ -1168,7 +1018,7 @@ lichess.numberFormat = (function() {
|
|||
});
|
||||
var $chat;
|
||||
cfg.element = element.querySelector('.round');
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
cfg.onChange = data.player.spectator ? $.noop : function(data) {
|
||||
var presets = [];
|
||||
if (data.steps.length < 4) presets = [
|
||||
|
@ -1581,7 +1431,7 @@ lichess.numberFormat = (function() {
|
|||
if (e.length) e.height(561 - e.offset().top);
|
||||
};
|
||||
resizeTimeline();
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
lichess.socket = lichess.StrongSocket(
|
||||
'/lobby/socket/v1',
|
||||
cfg.data.version, {
|
||||
receive: function(t, d) {
|
||||
|
@ -1646,7 +1496,7 @@ lichess.numberFormat = (function() {
|
|||
}
|
||||
});
|
||||
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
lobby = LichessLobby(element, cfg);
|
||||
|
||||
var $startButtons = $('#start_buttons');
|
||||
|
@ -1978,7 +1828,7 @@ lichess.numberFormat = (function() {
|
|||
messages: lichess_chat
|
||||
});
|
||||
var tournament;
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
lichess.socket = lichess.StrongSocket(
|
||||
'/tournament/' + cfg.data.id + '/socket/v1', cfg.data.socketVersion, {
|
||||
receive: function(t, d) {
|
||||
tournament.socketReceive(t, d)
|
||||
|
@ -1992,7 +1842,7 @@ lichess.numberFormat = (function() {
|
|||
name: "tournament"
|
||||
}
|
||||
});
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
tournament = LichessTournament(element, cfg);
|
||||
};
|
||||
|
||||
|
@ -2025,7 +1875,7 @@ lichess.numberFormat = (function() {
|
|||
messages: lichess_chat
|
||||
});
|
||||
var simul;
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
lichess.socket = lichess.StrongSocket(
|
||||
'/simul/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
|
||||
receive: function(t, d) {
|
||||
simul.socketReceive(t, d)
|
||||
|
@ -2039,7 +1889,7 @@ lichess.numberFormat = (function() {
|
|||
name: "simul"
|
||||
}
|
||||
});
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
simul = LichessSimul(element, cfg);
|
||||
};
|
||||
|
||||
|
@ -2056,7 +1906,7 @@ lichess.numberFormat = (function() {
|
|||
});
|
||||
var $watchers = $('#site_header div.watchers').watchers();
|
||||
var analyse, $panels;
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
lichess.socket = lichess.StrongSocket(
|
||||
data.url.socket,
|
||||
data.player.version, {
|
||||
options: {
|
||||
|
@ -2123,7 +1973,7 @@ lichess.numberFormat = (function() {
|
|||
};
|
||||
cfg.path = location.hash ? location.hash.replace(/#/, '') : '';
|
||||
cfg.element = element.querySelector('.analyse');
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
analyse = LichessAnalyse(cfg);
|
||||
cfg.jumpToIndex = analyse.jumpToIndex;
|
||||
|
||||
|
@ -2200,7 +2050,7 @@ lichess.numberFormat = (function() {
|
|||
var analyse;
|
||||
cfg.path = location.hash ? location.hash.replace(/#/, '') : '';
|
||||
cfg.element = element.querySelector('.analyse');
|
||||
lichess.socket = new lichess.StrongSocket('/socket', 0, {
|
||||
lichess.socket = lichess.StrongSocket('/socket', 0, {
|
||||
options: {
|
||||
name: "analyse"
|
||||
},
|
||||
|
@ -2211,7 +2061,7 @@ lichess.numberFormat = (function() {
|
|||
analyse.socketReceive(t, d);
|
||||
}
|
||||
});
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
cfg.socketSend = lichess.socket.send;
|
||||
analyse = LichessAnalyse(cfg);
|
||||
topMenuIntent();
|
||||
}
|
||||
|
|
|
@ -2,37 +2,266 @@
|
|||
// @compilation_level ADVANCED_OPTIMIZATIONS
|
||||
// ==/ClosureCompiler==
|
||||
|
||||
var lichess = window.lichess = window.lichess || {};
|
||||
|
||||
// versioned events, acks, retries, resync
|
||||
lichess.StrongSocket = function(url, version, settings) {
|
||||
var self = this;
|
||||
self.settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
|
||||
self.url = url;
|
||||
self.version = version;
|
||||
self.options = self.settings.options;
|
||||
self.ws = null;
|
||||
self.pingSchedule = null;
|
||||
self.connectSchedule = null;
|
||||
self.ackableMessages = [];
|
||||
self.lastPingTime = self.now();
|
||||
self.currentLag = 0;
|
||||
self.averageLag = 0;
|
||||
self.tryOtherUrl = false;
|
||||
self.autoReconnect = true;
|
||||
self.debug('Debug is enabled');
|
||||
if (self.options.resetUrl || self.options.prodPipe) {
|
||||
lichess.storage.remove(self.options.baseUrlKey);
|
||||
}
|
||||
if (self.options.prodPipe) {
|
||||
self.options.baseUrls = ['socket.en.lichess.org:9021'];
|
||||
}
|
||||
self.connect();
|
||||
window.addEventListener('unload', function() {
|
||||
self.destroy();
|
||||
});
|
||||
|
||||
var now = function() {
|
||||
return new Date().getTime();
|
||||
};
|
||||
|
||||
var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
|
||||
var url = url;
|
||||
var version = version;
|
||||
var options = settings.options;
|
||||
var ws = null;
|
||||
var pingSchedule = null;
|
||||
var connectSchedule = null;
|
||||
var ackableMessages = [];
|
||||
var lastPingTime = now();
|
||||
var currentLag = 0;
|
||||
var averageLag = 0;
|
||||
var tryOtherUrl = false;
|
||||
var autoReconnect = true;
|
||||
var nbConnects = 0;
|
||||
if (options.resetUrl || options.prodPipe) lichess.storage.remove(options.baseUrlKey);
|
||||
if (options.prodPipe) options.baseUrls = ['socket.en.lichess.org:9021'];
|
||||
|
||||
var connect = function() {
|
||||
destroy();
|
||||
autoReconnect = true;
|
||||
var fullUrl = "ws://" + baseUrl() + url + "?" + $.param(settings.params);
|
||||
debug("connection attempt to " + fullUrl, true);
|
||||
try {
|
||||
if (window.MozWebSocket) ws = new MozWebSocket(fullUrl);
|
||||
else if (window.WebSocket) ws = new WebSocket(fullUrl);
|
||||
else throw "[lila] no websockets found on this browser!";
|
||||
|
||||
ws.onerror = function(e) {
|
||||
onError(e);
|
||||
};
|
||||
ws.onclose = function(e) {
|
||||
if (autoReconnect) {
|
||||
debug('Will autoreconnect in ' + options.autoReconnectDelay);
|
||||
scheduleConnect(options.autoReconnectDelay);
|
||||
}
|
||||
};
|
||||
ws.onopen = function() {
|
||||
debug("connected to " + fullUrl, true);
|
||||
onSuccess();
|
||||
$('body').removeClass('offline');
|
||||
pingNow();
|
||||
$('body').trigger('socket.open');
|
||||
if ($('#user_tag').length) setTimeout(function() {
|
||||
send("following_onlines");
|
||||
}, 500);
|
||||
var resend = ackableMessages;
|
||||
ackableMessages = [];
|
||||
resend.forEach(function(x) {
|
||||
send(x.t, x.d);
|
||||
});
|
||||
};
|
||||
ws.onmessage = function(e) {
|
||||
var m = JSON.parse(e.data);
|
||||
// if (Math.random() > 0.5) {
|
||||
// console.log(m, 'skip');
|
||||
// return;
|
||||
// }
|
||||
if (m.t === 'n') pong();
|
||||
// else debug(e.data);
|
||||
if (m.t === 'b') m.d.forEach(handle);
|
||||
else handle(m);
|
||||
};
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
scheduleConnect(options.pingMaxLag);
|
||||
};
|
||||
|
||||
var send = function(t, d, o, again) {
|
||||
var data = d || {},
|
||||
options = o || {};
|
||||
if (options && options.ackable)
|
||||
ackableMessages.push({
|
||||
t: t,
|
||||
d: d
|
||||
});
|
||||
var message = JSON.stringify({
|
||||
t: t,
|
||||
d: data
|
||||
});
|
||||
debug("send " + message);
|
||||
try {
|
||||
ws.send(message);
|
||||
} catch (e) {
|
||||
// maybe sent before socket opens,
|
||||
// try again a second later,once.
|
||||
if (!again) setTimeout(function() {
|
||||
send(t, d, o, true);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
var sendAckable = function(t, d) {
|
||||
send(t, d, {
|
||||
ackable: true
|
||||
});
|
||||
};
|
||||
|
||||
var scheduleConnect = function(delay) {
|
||||
// debug('schedule connect ' + delay);
|
||||
clearTimeout(pingSchedule);
|
||||
clearTimeout(connectSchedule);
|
||||
connectSchedule = setTimeout(function() {
|
||||
$('body').addClass('offline');
|
||||
tryOtherUrl = true;
|
||||
connect();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
var schedulePing = function(delay) {
|
||||
clearTimeout(pingSchedule);
|
||||
pingSchedule = setTimeout(pingNow, delay);
|
||||
};
|
||||
|
||||
var pingNow = function() {
|
||||
clearTimeout(pingSchedule);
|
||||
clearTimeout(connectSchedule);
|
||||
try {
|
||||
ws.send(pingData());
|
||||
lastPingTime = now();
|
||||
} catch (e) {
|
||||
debug(e, true);
|
||||
}
|
||||
scheduleConnect(options.pingMaxLag);
|
||||
};
|
||||
|
||||
var pong = function() {
|
||||
clearTimeout(connectSchedule);
|
||||
schedulePing(options.pingDelay);
|
||||
currentLag = now() - lastPingTime;
|
||||
if (!averageLag) averageLag = currentLag;
|
||||
else averageLag = 0.2 * (currentLag - averageLag) + averageLag;
|
||||
if (options.lagTag) {
|
||||
options.lagTag.html(Math.round(averageLag));
|
||||
}
|
||||
};
|
||||
|
||||
var pingData = function() {
|
||||
return JSON.stringify({
|
||||
t: "p",
|
||||
v: version
|
||||
});
|
||||
};
|
||||
|
||||
var handle = function(m) {
|
||||
if (m.v) {
|
||||
if (m.v <= version) {
|
||||
debug("already has event " + m.v);
|
||||
return;
|
||||
}
|
||||
if (m.v > version + 1) {
|
||||
debug("event gap detected from " + version + " to " + m.v);
|
||||
if (!options.prodPipe) return;
|
||||
}
|
||||
version = m.v;
|
||||
}
|
||||
switch (m.t || false) {
|
||||
case false:
|
||||
break;
|
||||
case 'resync':
|
||||
if (!options.prodPipe) lichess.reload();
|
||||
break;
|
||||
case 'ack':
|
||||
ackableMessages = [];
|
||||
break;
|
||||
default:
|
||||
if (settings.receive) settings.receive(m.t, m.d);
|
||||
var h = settings.events[m.t];
|
||||
if (h) h(m.d || null, m);
|
||||
else if (!options.ignoreUnknownMessages) {
|
||||
debug('Message not supported ' + JSON.stringify(m));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var debug = function(msg, always) {
|
||||
if ((always || options.debug) && window.console && console.debug) {
|
||||
console.debug("[" + options.name + " " + settings.params.sri + "]", msg);
|
||||
}
|
||||
};
|
||||
|
||||
var destroy = function() {
|
||||
clearTimeout(pingSchedule);
|
||||
clearTimeout(connectSchedule);
|
||||
disconnect();
|
||||
ws = null;
|
||||
};
|
||||
|
||||
var disconnect = function() {
|
||||
if (ws) {
|
||||
debug("Disconnect", true);
|
||||
autoReconnect = false;
|
||||
ws.onerror = $.noop;
|
||||
ws.onclose = $.noop;
|
||||
ws.onopen = $.noop;
|
||||
ws.onmessage = $.noop;
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
|
||||
var onError = function(e) {
|
||||
options.debug = true;
|
||||
debug('error: ' + JSON.stringify(e));
|
||||
tryOtherUrl = true;
|
||||
setTimeout(function() {
|
||||
if (!$('#network_error').length) {
|
||||
var msg = "Your browser supports websockets, but cannot get a connection. Maybe you are behind a proxy that does not support websockets. Ask your system administrator to fix it!";
|
||||
$('#top').append('<span class="fright link text" id="network_error" title="' + msg + '" data-icon="j">Network error</span>');
|
||||
}
|
||||
}, 1000);
|
||||
clearTimeout(pingSchedule);
|
||||
};
|
||||
|
||||
var onSuccess = function() {
|
||||
$('#network_error').remove();
|
||||
nbConnects = (nbConnects || 0) + 1;
|
||||
if (nbConnects === 1) options.onFirstConnect();
|
||||
};
|
||||
|
||||
var baseUrl = function() {
|
||||
var key = options.baseUrlKey;
|
||||
var urls = options.baseUrls;
|
||||
var url = lichess.storage.get(key);
|
||||
if (!url) {
|
||||
url = urls[0];
|
||||
lichess.storage.set(key, url);
|
||||
} else if (tryOtherUrl) {
|
||||
tryOtherUrl = false;
|
||||
url = urls[(urls.indexOf(url) + 1) % urls.length];
|
||||
lichess.storage.set(key, url);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
connect();
|
||||
window.addEventListener('unload', destroy);
|
||||
|
||||
return {
|
||||
connect: connect,
|
||||
disconnect: disconnect,
|
||||
send: send,
|
||||
destroy: destroy,
|
||||
options: options,
|
||||
pingInterval: function() {
|
||||
return options.pingDelay + averageLag;
|
||||
},
|
||||
averageLag: function() {
|
||||
return averageLag;
|
||||
}
|
||||
};
|
||||
};
|
||||
lichess.StrongSocket.available = window.WebSocket || window.MozWebSocket;
|
||||
lichess.StrongSocket.sri = Math.random().toString(36).substring(2);
|
||||
lichess.StrongSocket.available = window.WebSocket || window.MozWebSocket;
|
||||
lichess.StrongSocket.defaults = {
|
||||
events: {
|
||||
fen: function(e) {
|
||||
|
@ -62,225 +291,3 @@ lichess.StrongSocket.defaults = {
|
|||
baseUrlKey: 'surl3'
|
||||
}
|
||||
};
|
||||
lichess.StrongSocket.prototype = {
|
||||
connect: function() {
|
||||
var self = this;
|
||||
self.destroy();
|
||||
self.autoReconnect = true;
|
||||
var fullUrl = "ws://" + self.baseUrl() + self.url + "?" + $.param(self.settings.params);
|
||||
self.debug("connection attempt to " + fullUrl, true);
|
||||
try {
|
||||
if (window.MozWebSocket) self.ws = new MozWebSocket(fullUrl);
|
||||
else if (window.WebSocket) self.ws = new WebSocket(fullUrl);
|
||||
else throw "[lila] no websockets found on this browser!";
|
||||
|
||||
self.ws.onerror = function(e) {
|
||||
self.onError(e);
|
||||
};
|
||||
self.ws.onclose = function(e) {
|
||||
if (self.autoReconnect) {
|
||||
self.debug('Will autoreconnect in ' + self.options.autoReconnectDelay);
|
||||
self.scheduleConnect(self.options.autoReconnectDelay);
|
||||
}
|
||||
};
|
||||
self.ws.onopen = function() {
|
||||
self.debug("connected to " + fullUrl, true);
|
||||
self.onSuccess();
|
||||
$('body').removeClass('offline');
|
||||
self.pingNow();
|
||||
$('body').trigger('socket.open');
|
||||
if ($('#user_tag').length) setTimeout(function() {
|
||||
self.send("following_onlines");
|
||||
}, 500);
|
||||
var resend = self.ackableMessages;
|
||||
self.ackableMessages = [];
|
||||
resend.forEach(function(x) {
|
||||
self.send(x.t, x.d);
|
||||
});
|
||||
};
|
||||
self.ws.onmessage = function(e) {
|
||||
var m = JSON.parse(e.data);
|
||||
// if (Math.random() > 0.5) {
|
||||
// console.log(m, 'skip');
|
||||
// return;
|
||||
// }
|
||||
if (m.t === 'n') self.pong();
|
||||
// else self.debug(e.data);
|
||||
if (m.t === 'b') m.d.forEach(function(mm) {
|
||||
self.handle(mm);
|
||||
});
|
||||
else self.handle(m);
|
||||
};
|
||||
} catch (e) {
|
||||
self.onError(e);
|
||||
}
|
||||
self.scheduleConnect(self.options.pingMaxLag);
|
||||
},
|
||||
send: function(t, d, o, again) {
|
||||
var self = this;
|
||||
var data = d || {},
|
||||
options = o || {};
|
||||
if (options && options.ackable)
|
||||
self.ackableMessages.push({
|
||||
t: t,
|
||||
d: d
|
||||
});
|
||||
var message = JSON.stringify({
|
||||
t: t,
|
||||
d: data
|
||||
});
|
||||
self.debug("send " + message);
|
||||
try {
|
||||
self.ws.send(message);
|
||||
} catch (e) {
|
||||
// maybe sent before socket opens,
|
||||
// try again a second later,once.
|
||||
if (!again) setTimeout(function() {
|
||||
this.send(t, d, o, true);
|
||||
}.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
sendAckable: function(t, d) {
|
||||
this.send(t, d, {
|
||||
ackable: true
|
||||
});
|
||||
},
|
||||
scheduleConnect: function(delay) {
|
||||
var self = this;
|
||||
// self.debug('schedule connect ' + delay);
|
||||
clearTimeout(self.pingSchedule);
|
||||
clearTimeout(self.connectSchedule);
|
||||
self.connectSchedule = setTimeout(function() {
|
||||
$('body').addClass('offline');
|
||||
self.tryOtherUrl = true;
|
||||
self.connect();
|
||||
}, delay);
|
||||
},
|
||||
schedulePing: function(delay) {
|
||||
var self = this;
|
||||
clearTimeout(self.pingSchedule);
|
||||
self.pingSchedule = setTimeout(function() {
|
||||
self.pingNow();
|
||||
}, delay);
|
||||
},
|
||||
pingNow: function() {
|
||||
var self = this;
|
||||
clearTimeout(self.pingSchedule);
|
||||
clearTimeout(self.connectSchedule);
|
||||
try {
|
||||
self.ws.send(self.pingData());
|
||||
self.lastPingTime = self.now();
|
||||
} catch (e) {
|
||||
self.debug(e, true);
|
||||
}
|
||||
self.scheduleConnect(self.options.pingMaxLag);
|
||||
},
|
||||
pong: function() {
|
||||
var self = this;
|
||||
clearTimeout(self.connectSchedule);
|
||||
self.schedulePing(self.options.pingDelay);
|
||||
self.currentLag = self.now() - self.lastPingTime;
|
||||
if (!self.averageLag) self.averageLag = self.currentLag;
|
||||
else self.averageLag = 0.2 * (self.currentLag - self.averageLag) + self.averageLag;
|
||||
if (self.options.lagTag) {
|
||||
self.options.lagTag.html(Math.round(self.averageLag));
|
||||
}
|
||||
},
|
||||
pingData: function() {
|
||||
return JSON.stringify({
|
||||
t: "p",
|
||||
v: this.version
|
||||
});
|
||||
},
|
||||
handle: function(m) {
|
||||
var self = this;
|
||||
if (m.v) {
|
||||
if (m.v <= self.version) {
|
||||
self.debug("already has event " + m.v);
|
||||
return;
|
||||
}
|
||||
if (m.v > self.version + 1) {
|
||||
self.debug("event gap detected from " + self.version + " to " + m.v);
|
||||
if (!self.options.prodPipe) return;
|
||||
}
|
||||
self.version = m.v;
|
||||
}
|
||||
switch (m.t || false) {
|
||||
case false:
|
||||
break;
|
||||
case 'resync':
|
||||
if (!self.options.prodPipe) lichess.reload();
|
||||
break;
|
||||
case 'ack':
|
||||
self.ackableMessages = [];
|
||||
break;
|
||||
default:
|
||||
if (self.settings.receive) self.settings.receive(m.t, m.d);
|
||||
var h = self.settings.events[m.t];
|
||||
if (h) h(m.d || null, m);
|
||||
else if (!self.options.ignoreUnknownMessages) {
|
||||
self.debug('Message not supported ' + JSON.stringify(m));
|
||||
}
|
||||
}
|
||||
},
|
||||
now: function() {
|
||||
return new Date().getTime();
|
||||
},
|
||||
debug: function(msg, always) {
|
||||
if ((always || this.options.debug) && window.console && console.debug) {
|
||||
console.debug("[" + this.options.name + " " + this.settings.params.sri + "]", msg);
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
clearTimeout(this.pingSchedule);
|
||||
clearTimeout(this.connectSchedule);
|
||||
this.disconnect();
|
||||
this.ws = null;
|
||||
},
|
||||
disconnect: function() {
|
||||
if (this.ws) {
|
||||
this.debug("Disconnect", true);
|
||||
this.autoReconnect = false;
|
||||
this.ws.onerror = $.noop;
|
||||
this.ws.onclose = $.noop;
|
||||
this.ws.onopen = $.noop;
|
||||
this.ws.onmessage = $.noop;
|
||||
this.ws.close();
|
||||
}
|
||||
},
|
||||
onError: function(e) {
|
||||
var self = this;
|
||||
self.options.debug = true;
|
||||
self.debug('error: ' + JSON.stringify(e));
|
||||
self.tryOtherUrl = true;
|
||||
setTimeout(function() {
|
||||
if (!$('#network_error').length) {
|
||||
var msg = "Your browser supports websockets, but cannot get a connection. Maybe you are behind a proxy that does not support websockets. Ask your system administrator to fix it!";
|
||||
$('#top').append('<span class="fright link text" id="network_error" title="' + msg + '" data-icon="j">Network error</span>');
|
||||
}
|
||||
}, 1000);
|
||||
clearTimeout(self.pingSchedule);
|
||||
},
|
||||
onSuccess: function() {
|
||||
$('#network_error').remove();
|
||||
this.nbConnects = (this.nbConnects || 0) + 1;
|
||||
if (this.nbConnects === 1) this.options.onFirstConnect();
|
||||
},
|
||||
baseUrl: function() {
|
||||
var key = this.options.baseUrlKey;
|
||||
var urls = this.options.baseUrls;
|
||||
var url = lichess.storage.get(key);
|
||||
if (!url) {
|
||||
url = urls[0];
|
||||
lichess.storage.set(key, url);
|
||||
} else if (this.tryOtherUrl) {
|
||||
this.tryOtherUrl = false;
|
||||
url = urls[(urls.indexOf(url) + 1) % urls.length];
|
||||
lichess.storage.set(key, url);
|
||||
}
|
||||
return url;
|
||||
},
|
||||
pingInterval: function() {
|
||||
return this.options.pingDelay + this.averageLag;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
// ==ClosureCompiler==
|
||||
// @compilation_level ADVANCED_OPTIMIZATIONS
|
||||
// ==/ClosureCompiler==
|
||||
|
||||
var lichess = window.lichess = window.lichess || {};
|
||||
|
||||
lichess.getParameterByName = function(name) {
|
||||
var match = RegExp('[?&]' + name + '=([^&]*)').exec(location.search);
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
||||
};
|
||||
|
||||
// declare now, populate later in a distinct script.
|
||||
var lichess_translations = lichess_translations || [];
|
||||
|
||||
function withStorage(f) {
|
||||
// can throw an exception when storage is full
|
||||
try {
|
||||
return !!window.localStorage ? f(window.localStorage) : null;
|
||||
} catch (e) {}
|
||||
}
|
||||
lichess.storage = {
|
||||
get: function(k) {
|
||||
return withStorage(function(s) {
|
||||
return s.getItem(k);
|
||||
});
|
||||
},
|
||||
remove: function(k) {
|
||||
withStorage(function(s) {
|
||||
s.removeItem(k);
|
||||
});
|
||||
},
|
||||
set: function(k, v) {
|
||||
// removing first may help http://stackoverflow.com/questions/2603682/is-anyone-else-receiving-a-quota-exceeded-err-on-their-ipad-when-accessing-local
|
||||
withStorage(function(s) {
|
||||
s.removeItem(k);
|
||||
s.setItem(k, v);
|
||||
});
|
||||
}
|
||||
};
|
||||
lichess.once = function(key, mod) {
|
||||
if (mod === 'always') return true;
|
||||
if (!lichess.storage.get(key)) {
|
||||
lichess.storage.set(key, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
lichess.trans = function(i18n) {
|
||||
return function(key) {
|
||||
var str = i18n[key] || key;
|
||||
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
|
||||
str = str.replace('%s', arg);
|
||||
});
|
||||
return str;
|
||||
};
|
||||
};
|
||||
lichess.widget = function(name, prototype) {
|
||||
var constructor = $[name] = function(options, element) {
|
||||
var self = this;
|
||||
self.element = $(element);
|
||||
$.data(element, name, self);
|
||||
self.options = options;
|
||||
self._create();
|
||||
};
|
||||
constructor.prototype = prototype;
|
||||
$.fn[name] = function(method) {
|
||||
var returnValue = this;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
if (typeof method === 'string') this.each(function() {
|
||||
var instance = $.data(this, name);
|
||||
if (!$.isFunction(instance[method]) || method.charAt(0) === "_")
|
||||
return $.error("no such method '" + method + "' for " + name + " widget instance");
|
||||
returnValue = instance[method].apply(instance, args);
|
||||
});
|
||||
else this.each(function() {
|
||||
if ($.data(this, name)) return $.error("widget " + name + " already bound to " + this);
|
||||
$.data(this, name, new constructor(method, this));
|
||||
});
|
||||
return returnValue;
|
||||
};
|
||||
};
|
||||
lichess.isTrident = navigator.userAgent.indexOf('Trident/') > -1;
|
||||
lichess.isChrome = navigator.userAgent.indexOf('Chrome/') > -1;
|
||||
lichess.isSafari = navigator.userAgent.indexOf('Safari/') > -1 && !lichess.isChrome;
|
||||
lichess.spinnerHtml = '<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>';
|
||||
lichess.assetUrl = function(url, noVersion) {
|
||||
return $('body').data('asset-url') + url + (noVersion ? '' : '?v=' + $('body').data('asset-version'));
|
||||
};
|
||||
lichess.loadCss = function(url) {
|
||||
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', lichess.assetUrl(url)));
|
||||
}
|
||||
lichess.loadScript = function(url, noVersion) {
|
||||
return $.ajax({
|
||||
dataType: "script",
|
||||
cache: true,
|
||||
url: lichess.assetUrl(url, noVersion)
|
||||
});
|
||||
};
|
||||
lichess.hopscotch = function(f) {
|
||||
lichess.loadCss('/assets/vendor/hopscotch/dist/css/hopscotch.min.css');
|
||||
lichess.loadScript("/assets/vendor/hopscotch/dist/js/hopscotch.min.js").done(f);
|
||||
}
|
||||
lichess.slider = function() {
|
||||
return lichess.loadScript('/assets/javascripts/vendor/jquery-ui.slider.min.js', true);
|
||||
};
|
||||
|
||||
lichess.isPageVisible = document.visibilityState !== 'hidden';
|
||||
lichess.notifications = [];
|
||||
// using document.hidden doesn't entirely work because it may return false if the window is not minimized but covered by other applications
|
||||
window.addEventListener('focus', function() {
|
||||
lichess.isPageVisible = true;
|
||||
lichess.notifications.forEach(function(n) {
|
||||
n.close();
|
||||
});
|
||||
lichess.notifications = [];
|
||||
});
|
||||
window.addEventListener('blur', function() {
|
||||
lichess.isPageVisible = false;
|
||||
});
|
||||
lichess.desktopNotification = function(msg) {
|
||||
var title = 'lichess.org';
|
||||
var icon = 'http://lichess1.org/assets/images/logo.256.png';
|
||||
var notify = function() {
|
||||
var notification = new Notification(title, {
|
||||
icon: icon,
|
||||
body: msg
|
||||
});
|
||||
notification.onclick = function() {
|
||||
window.focus();
|
||||
}
|
||||
lichess.notifications.push(notification);
|
||||
};
|
||||
if (lichess.isPageVisible || !('Notification' in window) || Notification.permission === 'denied') return;
|
||||
if (Notification.permission === 'granted') notify();
|
||||
else Notification.requestPermission(function(p) {
|
||||
if (p === 'granted') notify();
|
||||
});
|
||||
};
|
||||
lichess.unique = function(xs) {
|
||||
return xs.filter(function(x, i) {
|
||||
return xs.indexOf(x) === i;
|
||||
});
|
||||
};
|
||||
lichess.numberFormat = (function() {
|
||||
if (window.Intl && Intl.NumberFormat) {
|
||||
var formatter = new Intl.NumberFormat();
|
||||
return function(n) {
|
||||
return formatter.format(n);
|
||||
}
|
||||
}
|
||||
return function(n) {
|
||||
return n;
|
||||
};
|
||||
})();
|
|
@ -159,7 +159,7 @@ module.exports = function(opts) {
|
|||
};
|
||||
if (prom) move.promotion = prom;
|
||||
if (blur.get()) move.b = 1;
|
||||
if (this.clock) move.lag = Math.round(lichess.socket.averageLag);
|
||||
if (this.clock) move.lag = Math.round(lichess.socket.averageLag());
|
||||
this.resign(false);
|
||||
if (this.userId && this.data.pref.submitMove && !isPremove) {
|
||||
this.vm.moveToSubmit = move;
|
||||
|
@ -174,7 +174,7 @@ module.exports = function(opts) {
|
|||
role: role,
|
||||
pos: key
|
||||
};
|
||||
if (this.clock) drop.lag = Math.round(lichess.socket.averageLag);
|
||||
if (this.clock) drop.lag = Math.round(lichess.socket.averageLag());
|
||||
this.resign(false);
|
||||
if (this.userId && this.data.pref.submitMove && !isPredrop) {
|
||||
this.vm.dropToSubmit = drop;
|
||||
|
|
Loading…
Reference in New Issue