rewrite socket client to hide implementation

pull/1770/merge
Thibault Duplessis 2016-03-27 17:11:35 +07:00
parent 71a07976f4
commit f127f842df
7 changed files with 431 additions and 419 deletions

View File

@ -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")
}

View File

@ -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

View File

@ -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();

View File

@ -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();
}

View File

@ -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;
}
};

View File

@ -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;
};
})();

View File

@ -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;