From bae751bc6bc548791772c3ff5883a03deeb77264 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 31 May 2021 00:58:12 +0200 Subject: [PATCH] Rollback last (bad) improving attempt --- client/src/views/Game.vue | 122 +++++++++++++++++++++++------------ client/src/views/Hall.vue | 98 +++++++++++++--------------- client/src/views/MyGames.vue | 38 +++++------ server/sockets.js | 19 ------ 4 files changed, 142 insertions(+), 135 deletions(-) diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 8c5e7c28..1071ece8 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -231,13 +231,17 @@ export default { conn: null, roomInitialized: false, // If asklastate got no reply, ask again: + gotLastate: false, gotMoveIdx: -1, //last move index received - opponentGotMove: false, //used to freeze clock + // If newmove got no pingback, send again: + opponentGotMove: false, connexionString: "", - reopenTimeout: 0, - reconnectTimeout: 0, - // Incomplete info games: show move played: + socketCloseListener: 0, + // Incomplete info games: show move played moveNotation: "", + // Intervals from setInterval(): + askLastate: null, + retrySendmove: null, clockUpdate: null, // Related to (killing of) self multi-connects: newConnect: {} @@ -285,11 +289,14 @@ export default { }, methods: { cleanBeforeDestroy: function() { + clearInterval(this.socketCloseListener); document.removeEventListener('visibilitychange', this.visibilityChange); window.removeEventListener('focus', this.onFocus); window.removeEventListener('blur', this.onBlur); + if (!!this.askLastate) clearInterval(this.askLastate); + if (!!this.retrySendmove) clearInterval(this.retrySendmove); if (!!this.clockUpdate) clearInterval(this.clockUpdate); - clearTimeout(this.reopenTimeout); + this.conn.removeEventListener("message", this.socketMessageListener); this.send("disconnect"); this.conn = null; }, @@ -331,6 +338,23 @@ export default { //const oppSid = // this.game.players.find(p => p.sid != this.st.user.sid).sid; this.send("asklastate", { target: sid }); + let counter = 1; + this.askLastate = setInterval( + () => { + // Ask at most 3 times: + // if no reply after that there should be a network issue. + if ( + counter < 3 && + !this.gotLastate && + !!this.people[sid] + ) { + this.send("asklastate", { target: sid }); + counter++; + } + else clearInterval(this.askLastate); + }, + 1500 + ); }, atCreation: function() { document.addEventListener('visibilitychange', this.visibilityChange); @@ -369,8 +393,11 @@ export default { this.rematchOffer = ""; this.lastate = undefined; this.roomInitialized = false; + this.gotLastate = false; this.gotMoveIdx = -1; this.opponentGotMove = false; + this.askLastate = null; + this.retrySendmove = null; this.clockUpdate = null; this.newConnect = {}; // 1] Initialize connection @@ -382,41 +409,30 @@ export default { "&page=" + // Discard potential "/?next=[...]" for page indication: encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]); - this.openConnection(); - }, - openConnection: function() { this.conn = new WebSocket(this.connexionString); - const onOpen = () => { - this.reconnectTimeout = 250; - const oppSid = this.getOppsid(); - if (!!oppSid) this.send("asklastate", { target: oppSid }); - }; - this.conn.onopen = onOpen; - this.conn.onmessage = this.socketMessageListener; - const closeConnection = () => { - this.reopenTimeout = setTimeout( - () => { - this.openConnection(); - this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000); - }, - this.reconnectTimeout - ); - }; - this.conn.onerror = closeConnection; - this.conn.onclose = closeConnection; + this.conn.addEventListener("message", this.socketMessageListener); + this.socketCloseListener = setInterval( + () => { + if (this.conn.readyState == 3) { + this.conn.removeEventListener( + "message", this.socketMessageListener); + this.conn = new WebSocket(this.connexionString); + this.conn.addEventListener("message", this.socketMessageListener); + const oppSid = this.getOppsid(); + if (!!oppSid) this.requestLastate(oppSid); //in case of + } + }, + 1000 + ); // Socket init required before loading remote game: const socketInit = callback => { if (this.conn.readyState == 1) // 1 == OPEN state callback(); - else { + else // Socket not ready yet (initial loading) // NOTE: first arg is Websocket object, unused here: - this.conn.onopen = () => { - onOpen(); - callback(); - }; - } + this.conn.onopen = () => callback(); }; this.fetchGame((game) => { if (!!game) { @@ -447,13 +463,8 @@ export default { } }, send: function(code, obj) { - let timeout = 0; - const trySend = () => { - if (!!this.conn && this.conn.readyState == 1) - this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); - else setTimeout(trySend, timeout = (timeout > 0 ? 2 * timeout : 250)); - } - setTimeout(trySend, timeout); + if (!!this.conn && this.conn.readyState == 1) + this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); }, isConnected: function(index) { const player = this.game.players[index]; @@ -681,11 +692,12 @@ export default { } // Ask potentially missed last state, if opponent and I play if ( + !this.gotLastate && !!this.game.mycolor && this.game.type == "live" && this.game.players.some(p => p.sid == user.sid) ) { - this.send("asklastate", { target: user.sid }); + this.requestLastate(user.sid); } break; } @@ -745,6 +757,7 @@ export default { // Confirm scenario? Fix? case "lastate": { // Got opponent infos about last move + this.gotLastate = true; this.lastate = data.data; if (this.lastate.movesCount - 1 > this.gotMoveIdx) this.gotMoveIdx = this.lastate.movesCount - 1; @@ -1300,7 +1313,6 @@ export default { ).replace(/(fen:)([^:]*):/g, replaceByDiag); }; let variant = undefined; - // TODO: avoid setInterval() here const trySetVname = setInterval( () => { // this.st.variants might be uninitialized (variant == null) @@ -1529,7 +1541,33 @@ export default { sendMove["clock"] = this.game.clocks[colorIdx]; // (Live) Clocks will re-start when the opponent pingback arrive this.opponentGotMove = false; - this.send("newmove", { data: sendMove }); + this.send("newmove", {data: sendMove}); + // If the opponent doesn't reply gotmove soon enough, re-send move: + // Do this at most 2 times, because more would mean network issues, + // opponent would then be expected to disconnect/reconnect. + let counter = 1; + const currentUrl = document.location.href; + this.retrySendmove = setInterval( + () => { + if ( + counter >= 3 || + this.opponentGotMove || + document.location.href != currentUrl //page change + ) { + clearInterval(this.retrySendmove); + return; + } + const oppsid = this.getOppsid(); + if (!oppsid) + // Opponent is disconnected: he'll ask last state + clearInterval(this.retrySendmove); + else { + this.send("newmove", { data: sendMove, target: oppsid }); + counter++; + } + }, + 1500 + ); } else // Not my move or I'm an observer: just start other player's clock diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 4f27f794..94a7bbad 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -282,8 +282,7 @@ export default { presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"), conn: null, connexionString: "", - reopenTimeout: 0, - reconnectTimeout: 0, + socketCloseListener: 0, // Related to (killing of) self multi-connects: newConnect: {} }; @@ -322,31 +321,35 @@ export default { } } ); - if (!!this.$route.query["challenge"]) { - // Automatic challenge sending, for tournaments - this.loadNewchallVariant( - () => { - Object.assign( - this.newchallenge, - { - fen: "", - vid: - this.st.variants - .find(v => v.name == this.$route.query["variant"]) - .id, - to: this.$route.query["challenge"], - color: this.$route.query["color"] || '', - cadence: this.$route.query["cadence"], - options: {}, - memorize: false - } - ); - window.doClick("modalNewgame"); - }, - this.$route.query["variant"] - ); - } - // Connexion string won't change if disconnect/reconnect + const connectAndPoll = () => { + this.send("connect"); + this.send("pollclientsandgamers"); + if (!!this.$route.query["challenge"]) { + // Automatic challenge sending, for tournaments + this.loadNewchallVariant( + () => { + Object.assign( + this.newchallenge, + { + fen: "", + vid: + this.st.variants + .find(v => v.name == this.$route.query["variant"]) + .id, + to: this.$route.query["challenge"], + color: this.$route.query["color"] || '', + cadence: this.$route.query["cadence"], + options: {}, + memorize: false + } + ); + window.doClick("modalNewgame"); + }, + this.$route.query["variant"] + ); + } + }; + // Initialize connection this.connexionString = params.socketUrl + "/?sid=" + this.st.user.sid + @@ -355,7 +358,19 @@ export default { "&page=" + // Hall: path is "/" (TODO: could be hard-coded as well) encodeURIComponent(this.$route.path); - this.openConnection(); + this.conn = new WebSocket(this.connexionString); + this.conn.onopen = connectAndPoll; + this.conn.addEventListener("message", this.socketMessageListener); + this.socketCloseListener = setInterval( + () => { + if (this.conn.readyState == 3) { + this.conn.removeEventListener("message", this.socketMessageListener); + this.conn = new WebSocket(this.connexionString); + this.conn.addEventListener("message", this.socketMessageListener); + } + }, + 1000 + ); }, mounted: function() { document.getElementById("peopleWrap") @@ -451,36 +466,13 @@ export default { this.cleanBeforeDestroy(); }, methods: { - openConnection: function() { - const connectAndPoll = () => { - this.send("connect"); - this.send("pollclientsandgamers"); - }; - // Initialize connection - this.conn = new WebSocket(this.connexionString); - this.conn.onopen = () => { - this.reconnectTimeout = 250; - connectAndPoll(); - }; - this.conn.onmessage = this.socketMessageListener; - const closeConnection = () => { - this.reopenTimeout = setTimeout( - () => { - this.openConnection(); - this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000); - }, - this.reconnectTimeout - ); - }; - this.conn.onerror = closeConnection; - this.conn.onclose = closeConnection; - }, cleanBeforeDestroy: function() { + clearInterval(this.socketCloseListener); document.removeEventListener('visibilitychange', this.visibilityChange); window.removeEventListener('focus', this.onFocus); window.removeEventListener('blur', this.onBlur); window.removeEventListener("beforeunload", this.cleanBeforeDestroy); - clearTimeout(this.reopenTimeout); + this.conn.removeEventListener("message", this.socketMessageListener); this.send("disconnect"); this.conn = null; }, diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index ad330aec..d12fff48 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -78,8 +78,7 @@ export default { }, conn: null, connexionString: "", - reopenTimeout: 0, - reconnectTimeout: 0 + socketCloseListener: 0 }; }, watch: { @@ -97,6 +96,7 @@ export default { }, created: function() { window.addEventListener("beforeunload", this.cleanBeforeDestroy); + // Initialize connection this.connexionString = params.socketUrl + "/?sid=" + this.st.user.sid + @@ -104,7 +104,19 @@ export default { "&tmpId=" + getRandString() + "&page=" + encodeURIComponent(this.$route.path); - this.openConnection(); + this.conn = new WebSocket(this.connexionString); + this.conn.onmessage = this.socketMessageListener; + this.socketCloseListener = setInterval( + () => { + if (this.conn.readyState == 3) { + // Connexion is closed: re-open + this.conn.removeEventListener("message", this.socketMessageListener); + this.conn = new WebSocket(this.connexionString); + this.conn.addEventListener("message", this.socketMessageListener); + } + }, + 1000 + ); }, mounted: function() { const adjustAndSetDisplay = () => { @@ -170,26 +182,10 @@ export default { this.cleanBeforeDestroy(); }, methods: { - openConnection: function() { - // Initialize connection - this.conn = new WebSocket(this.connexionString); - this.conn.onopen = () => { this.reconnectTimeout = 250; }; - this.conn.onmessage = this.socketMessageListener; - const closeConnection = () => { - this.reopenTimeout = setTimeout( - () => { - this.openConnection(); - this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000); - }, - this.reconnectTimeout - ); - }; - this.conn.onerror = closeConnection; - this.conn.onclose = closeConnection; - }, cleanBeforeDestroy: function() { + clearInterval(this.socketCloseListener); window.removeEventListener("beforeunload", this.cleanBeforeDestroy); - clearTimeout(this.reopenTimeout); + this.conn.removeEventListener("message", this.socketMessageListener); this.conn.send(JSON.stringify({code: "disconnect"})); this.conn = null; }, diff --git a/server/sockets.js b/server/sockets.js index 520b8b70..c2b41e88 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -18,12 +18,6 @@ function send(socket, message) { socket.send(JSON.stringify(message)); } -// https://www.npmjs.com/package/ws - detect lost connections... -function noop() {} -function heartbeat() { - this.isAlive = true; -} - module.exports = function(wss) { // Associative array page --> sid --> tmpId --> socket // "page" is either "/" for hall or "/game/some_gid" for Game, @@ -42,8 +36,6 @@ module.exports = function(wss) { }); } wss.on("connection", (socket, req) => { - socket.isAlive = true; - socket.on('pong', heartbeat); const query = getJsonFromUrl(req.url); const sid = query["sid"]; const id = query["id"]; @@ -376,15 +368,4 @@ module.exports = function(wss) { socket.on("message", messageListener); socket.on("close", closeListener); }); - const interval = setInterval( - () => { - wss.clients.forEach(ws => { - if (ws.isAlive === false) return ws.terminate(); - ws.isAlive = false; - ws.ping(noop); - }); - }, - 30000 - ); - wss.on('close', () => clearInterval(interval)); } -- 2.44.0