From 882ec6fe88ac4a536dd25eca5fd50ff3d27f7994 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 24 Mar 2020 13:41:58 +0100
Subject: [PATCH] Attempt to get rid of 'zombie Websocket' error

---
 client/src/views/Game.vue    | 19 ++++++++++++-------
 client/src/views/Hall.vue    | 22 +++++++++++++---------
 client/src/views/MyGames.vue | 22 ++++++++++++++--------
 3 files changed, 39 insertions(+), 24 deletions(-)

diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index 13eab11f..31718554 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -176,6 +176,7 @@ export default {
       // If newmove got no pingback, send again:
       opponentGotMove: false,
       connexionString: "",
+      socketCloseListener: 0,
       // Incomplete info games: show move played
       moveNotation: "",
       // Intervals from setInterval():
@@ -226,12 +227,12 @@ export default {
   },
   methods: {
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       document.removeEventListener('visibilitychange', this.visibilityChange);
       if (!!this.askLastate) clearInterval(this.askLastate);
       if (!!this.retrySendmove) clearInterval(this.retrySendmove);
       if (!!this.clockUpdate) clearInterval(this.clockUpdate);
       this.conn.removeEventListener("message", this.socketMessageListener);
-      this.conn.removeEventListener("close", this.socketCloseListener);
       this.send("disconnect");
       this.conn = null;
     },
@@ -308,7 +309,16 @@ export default {
         encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]);
       this.conn = new WebSocket(this.connexionString);
       this.conn.addEventListener("message", this.socketMessageListener);
-      this.conn.addEventListener("close", this.socketCloseListener);
+      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
+      );
       // Socket init required before loading remote game:
       const socketInit = callback => {
         if (this.conn.readyState == 1)
@@ -751,11 +761,6 @@ export default {
           break;
       }
     },
-    socketCloseListener: function() {
-      this.conn = new WebSocket(this.connexionString);
-      this.conn.addEventListener("message", this.socketMessageListener);
-      this.conn.addEventListener("close", this.socketCloseListener);
-    },
     updateCorrGame: function(obj, callback) {
       ajax(
         "/games",
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index e60d982f..132c31ba 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -253,6 +253,7 @@ export default {
       presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"),
       conn: null,
       connexionString: "",
+      socketCloseListener: 0,
       // Related to (killing of) self multi-connects:
       newConnect: {},
       killed: {}
@@ -306,7 +307,16 @@ export default {
     this.conn = new WebSocket(this.connexionString);
     this.conn.onopen = connectAndPoll;
     this.conn.addEventListener("message", this.socketMessageListener);
-    this.conn.addEventListener("close", this.socketCloseListener);
+    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() {
     ["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => {
@@ -396,10 +406,10 @@ export default {
   },
   methods: {
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       document.removeEventListener('visibilitychange', this.visibilityChange);
       window.removeEventListener("beforeunload", this.cleanBeforeDestroy);
       this.conn.removeEventListener("message", this.socketMessageListener);
-      this.conn.removeEventListener("close", this.socketCloseListener);
       this.send("disconnect");
       this.conn = null;
     },
@@ -595,7 +605,7 @@ export default {
               // Do not set name or id: identity unknown yet
               this.people[sid] = { tmpIds: data.sockIds[sid] };
             else
-              Object.assign(this.people[s.sid].tmpIds, data.sockIds[sid]);
+              Object.assign(this.people[sid].tmpIds, data.sockIds[sid]);
             if (Object.values(data.sockIds[sid]).some(v => v.page == "/"))
               // Peer is in Hall
               this.send("askchallenges", { target: sid });
@@ -838,12 +848,6 @@ export default {
           break;
       }
     },
-    socketCloseListener: function() {
-      if (!this.conn) return;
-      this.conn = new WebSocket(this.connexionString);
-      this.conn.addEventListener("message", this.socketMessageListener);
-      this.conn.addEventListener("close", this.socketCloseListener);
-    },
     loadMoreCorr: function() {
       ajax(
         "/observedgames",
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index 94035287..d1079ee4 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -55,7 +55,8 @@ export default {
       // hasMore == TRUE: a priori there could be more games to load
       hasMore: { live: true, corr: store.state.user.id > 0 },
       conn: null,
-      connexionString: ""
+      connexionString: "",
+      socketCloseListener: 0
     };
   },
   watch: {
@@ -75,7 +76,17 @@ export default {
       encodeURIComponent(this.$route.path);
     this.conn = new WebSocket(this.connexionString);
     this.conn.onmessage = this.socketMessageListener;
-    this.conn.onclose = this.socketCloseListener;
+    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 = () => {
@@ -134,9 +145,9 @@ export default {
   },
   methods: {
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       window.removeEventListener("beforeunload", this.cleanBeforeDestroy);
       this.conn.removeEventListener("message", this.socketMessageListener);
-      this.conn.removeEventListener("close", this.socketCloseListener);
       this.conn.send(JSON.stringify({code: "disconnect"}));
       this.conn = null;
     },
@@ -228,11 +239,6 @@ export default {
         }
       }
     },
-    socketCloseListener: function() {
-      this.conn = new WebSocket(this.connexionString);
-      this.conn.addEventListener("message", this.socketMessageListener);
-      this.conn.addEventListener("close", this.socketCloseListener);
-    },
     showGame: function(game) {
       if (game.type == "live" || !game.myTurn) {
         this.$router.push("/game/" + game.id);
-- 
2.44.0