Fixing attempt: clients now track tmpIds to know who is online/onfocus. Bug fixed...
[vchess.git] / client / src / views / Game.vue
index 6339a3c..13eab11 100644 (file)
@@ -26,13 +26,10 @@ main
         span {{ st.tr["Participant(s):"] }} 
         span(
           v-for="p in Object.values(people)"
-          v-if="p.focus && !!p.name"
+          v-if="participateInChat(p)"
         )
           | {{ p.name }} 
-        span.anonymous(
-          v-if="Object.values(people).some(p => p.focus && !p.name)"
-        )
-          | + @nonymous
+        span.anonymous(v-if="someAnonymousPresent()") + @nonymous
       Chat(
         ref="chatcomp"
         :players="game.players"
@@ -192,7 +189,10 @@ export default {
   },
   watch: {
     $route: function(to, from) {
-      if (from.params["id"] != to.params["id"]) {
+      if (to.path.length < 6 || to.path.substr(0, 6) != "/game/")
+        // Page change
+        this.cleanBeforeDestroy();
+      else if (from.params["id"] != to.params["id"]) {
         // Change everything:
         this.cleanBeforeDestroy();
         let boardDiv = document.querySelector(".game");
@@ -210,7 +210,6 @@ export default {
     this.atCreation();
   },
   mounted: function() {
-    document.addEventListener('visibilitychange', this.visibilityChange);
     ["chatWrap", "infoDiv"].forEach(eltName => {
       document.getElementById(eltName)
         .addEventListener("click", processModalClick);
@@ -223,10 +222,19 @@ export default {
     }
   },
   beforeDestroy: function() {
-    document.removeEventListener('visibilitychange', this.visibilityChange);
     this.cleanBeforeDestroy();
   },
   methods: {
+    cleanBeforeDestroy: function() {
+      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;
+    },
     visibilityChange: function() {
       // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27
       this.send(
@@ -235,20 +243,34 @@ export default {
           : "losefocus"
       );
     },
+    participateInChat: function(p) {
+      return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name;
+    },
+    someAnonymousPresent: function() {
+      return (
+        Object.values(this.people).some(p =>
+          !p.name && Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus)
+        )
+      );
+    },
     atCreation: function() {
+      document.addEventListener('visibilitychange', this.visibilityChange);
       // 0] (Re)Set variables
       this.gameRef = this.$route.params["id"];
       // next = next corr games IDs to navigate faster (if applicable)
       this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
       // Always add myself to players' list
       const my = this.st.user;
+      const tmpId = getRandString();
       this.$set(
         this.people,
         my.sid,
         {
           id: my.id,
           name: my.name,
-          focus: true
+          tmpIds: {
+            tmpId: { focus: true }
+          }
         }
       );
       this.game = {
@@ -278,12 +300,9 @@ export default {
       // 1] Initialize connection
       this.connexionString =
         params.socketUrl +
-        "/?sid=" +
-        this.st.user.sid +
-        "&id=" +
-        this.st.user.id +
-        "&tmpId=" +
-        getRandString() +
+        "/?sid=" + this.st.user.sid +
+        "&id=" + this.st.user.id +
+        "&tmpId=" + tmpId +
         "&page=" +
         // Discard potential "/?next=[...]" for page indication:
         encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]);
@@ -310,15 +329,6 @@ export default {
           socketInit(() => { this.send("askfullgame"); });
       });
     },
-    cleanBeforeDestroy: function() {
-      if (!!this.askLastate)
-        clearInterval(this.askLastate);
-      if (!!this.retrySendmove)
-        clearInterval(this.retrySendmove);
-      if (!!this.clockUpdate)
-        clearInterval(this.clockUpdate);
-      this.send("disconnect");
-    },
     roomInit: function() {
       if (!this.roomInitialized) {
         // Notify the room only now that I connected, because
@@ -345,14 +355,22 @@ export default {
       return (
         (
           !!player.sid &&
-          Object.keys(this.people).some(sid =>
-            sid == player.sid && this.people[sid].focus)
+          Object.keys(this.people).some(sid => {
+            return (
+              sid == player.sid &&
+              Object.values(this.people[sid].tmpIds).some(v => v.focus)
+            );
+          })
         )
         ||
         (
           !!player.id &&
-          Object.values(this.people).some(p =>
-            p.id == player.id && p.focus)
+          Object.values(this.people).some(p => {
+            return (
+              p.id == player.id &&
+              Object.values(p.tmpIds).some(v => v.focus)
+            );
+          })
         )
       );
     },
@@ -443,37 +461,54 @@ export default {
       const data = JSON.parse(msg.data);
       switch (data.code) {
         case "pollclients":
-          // TODO: shuffling and random filtering on server, if
-          // the room is really crowded.
-          data.sockIds.forEach(sid => {
+          // TODO: shuffling and random filtering on server,
+          // if the room is really crowded.
+          Object.keys(data.sockIds).forEach(sid => {
+            // TODO: test sid != user.sid was already done on server
             if (sid != this.st.user.sid) {
-              this.people[sid] = { focus: true };
+              this.people[sid] = { tmpIds: data.sockIds[sid] };
               this.send("askidentity", { target: sid });
             }
           });
           break;
         case "connect":
-          if (!this.people[data.from]) {
-            this.people[data.from] = { focus: true };
+          if (!this.people[data.from[0]]) {
+            // focus depends on the tmpId (e.g. tab)
+            this.$set(
+              this.people,
+              data.from[0],
+              {
+                tmpIds: {
+                  [data.from[1]]: { focus: true }
+                }
+              }
+            );
             this.newConnect[data.from] = true; //for self multi-connects tests
-            this.send("askidentity", { target: data.from });
+            this.send("askidentity", { target: data.from[0] });
+          } else {
+            this.people[data.from[0]].tmpIds[data.from[1]] = { focus: true };
+            this.$forceUpdate(); //TODO: shouldn't be required
           }
           break;
         case "disconnect":
-          this.$delete(this.people, data.from);
+          if (!this.people[data.from[0]]) return;
+          delete this.people[data.from[0]].tmpIds[data.from[1]];
+          if (Object.keys(this.people[data.from[0]].tmpIds).length == 0)
+            this.$delete(this.people, data.from[0]);
+          else this.$forceUpdate(); //TODO: shouldn't be required
           break;
         case "getfocus": {
-          let player = this.people[data.from];
+          let player = this.people[data.from[0]];
           if (!!player) {
-            player.focus = true;
+            player.tmpIds[data.from[1]].focus = true;
             this.$forceUpdate(); //TODO: shouldn't be required
           }
           break;
         }
         case "losefocus": {
-          let player = this.people[data.from];
+          let player = this.people[data.from[0]];
           if (!!player) {
-            player.focus = false;
+            player.tmpIds[data.from[1]].focus = false;
             this.$forceUpdate(); //TODO: shouldn't be required
           }
           break;
@@ -499,7 +534,7 @@ export default {
         case "identity": {
           const user = data.data;
           let player = this.people[user.sid];
-          // player.focus is already set
+          // player.tmpIds is already set
           player.name = user.name;
           player.id = user.id;
           this.$forceUpdate(); //TODO: shouldn't be required
@@ -519,6 +554,7 @@ export default {
           if (!this.killed[this.st.user.sid]) {
             // Ask potentially missed last state, if opponent and I play
             if (
+              !this.gotLastate &&
               !!this.game.mycolor &&
               this.game.type == "live" &&
               this.game.score == "*" &&
@@ -587,6 +623,7 @@ export default {
         case "asklastate":
           // Sending informative last state if I played a move or score != "*"
           // If the game or moves aren't loaded yet, delay the sending:
+          // TODO: since socket init after game load, the game is supposedly ready
           if (!this.game || !this.game.moves) this.lastateAsked = true;
           else this.sendLastate(data.from);
           break;
@@ -650,6 +687,9 @@ export default {
           this.opponentGotMove = true;
           // Now his clock starts running on my side:
           const oppIdx = ['w','b'].indexOf(this.vr.turn);
+          // NOTE: next line to avoid multi-resetClocks when several tabs
+          // on same game, resulting in a faster countdown.
+          if (!!this.clockUpdate) clearInterval(this.clockUpdate);
           this.re_setClocks();
           break;
         }
@@ -763,7 +803,7 @@ export default {
         this.$refs["basegame"].play(data.lastMove, "received", null, true);
         this.processMove(data.lastMove);
       } else {
-        clearInterval(this.clockUpdate);
+        if (!!this.clockUpdate) clearInterval(this.clockUpdate);
         this.re_setClocks();
       }
       if (data.drawSent) this.drawOffer = "received";
@@ -842,7 +882,7 @@ export default {
           this.send("rnewgame", { data: gameInfo, oppsid: oppsid });
           // To main Hall if corr game:
           if (this.game.type == "corr")
-            this.send("newgame", { data: gameInfo });
+            this.send("newgame", { data: gameInfo, page: "/" });
           // Also to MyGames page:
           this.notifyMyGames("newgame", gameInfo);
         };
@@ -1090,6 +1130,7 @@ export default {
             this.game.score != "*"
           ) {
             clearInterval(this.clockUpdate);
+            this.clockUpdate = null;
             if (this.game.clocks[colorIdx] < 0)
               this.gameOver(
                 currentTurn == "w" ? "0-1" : "1-0",
@@ -1116,6 +1157,7 @@ export default {
         const origMovescount = this.game.moves.length;
         // The move is (about to be) played: stop clock
         clearInterval(this.clockUpdate);
+        this.clockUpdate = null;
         if (moveCol == this.game.mycolor && !data.receiveMyMove) {
           if (this.drawOffer == "received")
             // I refuse draw
@@ -1409,7 +1451,7 @@ button
   margin: 0
   display: inline-flex
   img
-    height: 24px
+    height: 22px
     display: flex
     @media screen and (max-width: 767px)
       height: 18px