From 910d631b73cad5ffef1b4461157b704e7e7057d8 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 18 Feb 2020 18:02:44 +0100
Subject: [PATCH] Some more cleaning + fixes

---
 client/package.json                     |   4 +-
 client/src/App.vue                      |  13 +--
 client/src/base_rules.js                |   3 +-
 client/src/components/BaseGame.vue      |  24 +++---
 client/src/components/ChallengeList.vue |   8 +-
 client/src/components/Chat.vue          |  13 ++-
 client/src/components/ComputerGame.vue  |   9 ++-
 client/src/components/ContactForm.vue   |  13 ++-
 client/src/components/GameList.vue      |  14 +++-
 client/src/components/Language.vue      |   5 +-
 client/src/components/MoveList.vue      |  62 +++++++++++++-
 client/src/components/Settings.vue      |  15 +++-
 client/src/components/UpsertUser.vue    |  42 ++++++++--
 client/src/data/userCheck.js            |   2 +
 client/src/store.js                     |  11 +--
 client/src/utils/cookie.js              |   8 +-
 client/src/utils/gameStorage.js         |   2 +-
 client/src/variants/Grand.js            |   1 -
 client/src/variants/Magnetic.js         |   6 +-
 client/src/views/Analyse.vue            |  10 ++-
 client/src/views/Auth.vue               |  29 ++-----
 client/src/views/Game.vue               |  51 +++++++++---
 client/src/views/Hall.vue               | 103 ++++++++++++++++++------
 client/src/views/Logout.vue             |  37 ++++-----
 client/src/views/MyGames.vue            |  20 +++--
 client/src/views/News.vue               |  27 +++++--
 client/src/views/Problems.vue           |  22 ++++-
 client/src/views/Rules.vue              |  34 ++++++--
 client/src/views/Variants.vue           |   7 +-
 server/sockets.js                       |  21 +++--
 30 files changed, 441 insertions(+), 175 deletions(-)

diff --git a/client/package.json b/client/package.json
index 88feedb9..172b5c4c 100644
--- a/client/package.json
+++ b/client/package.json
@@ -64,13 +64,13 @@
           "flatTernaryExpressions": true
         }
       ],
-      "no-else-return"   : [
+      "no-else-return": [
         1,
         {
           "allowElseIf": false
         }
       ],
-      "semi"             : [1, "always"]
+      "semi": [1, "always"]
     }
   },
   "postcss": {
diff --git a/client/src/App.vue b/client/src/App.vue
index 9879e275..9c724e76 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -25,19 +25,22 @@
               router-link(to="/problems")
                 | {{ st.tr["Problems"] }}
             #rightMenu
-              .clickable(onClick="doClick('modalUser')")
+              .clickable(onClick="window.doClick('modalUser')")
                 | {{ st.user.id > 0 ? (st.user.name || "@nonymous") : "Login" }}
-              .clickable(onClick="doClick('modalSettings')")
+              .clickable(onClick="window.doClick('modalSettings')")
                 | {{ st.tr["Settings"] }}
-              .clickable#flagContainer(onClick="doClick('modalLang')")
-                img(v-if="!!st.lang" :src="flagImage")
+              .clickable#flagContainer(onClick="window.doClick('modalLang')")
+                img(
+                  v-if="!!st.lang"
+                  :src="flagImage"
+                )
     router-view
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       footer
         router-link.menuitem(to="/about") {{ st.tr["About"] }}
         router-link.menuitem(to="/news") {{ st.tr["News"] }}
-        p.clickable(onClick="doClick('modalContact')")
+        p.clickable(onClick="window.doClick('modalContact')")
           | {{ st.tr["Contact"] }}
 </template>
 
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 056f4774..42d9bdc8 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -4,8 +4,8 @@
 import { ArrayFun } from "@/utils/array";
 import { randInt, shuffle } from "@/utils/alea";
 
+// class "PiPo": Piece + Position
 export const PiPo = class PiPo {
-  //Piece+Position
   // o: {piece[p], color[c], posX[x], posY[y]}
   constructor(o) {
     this.p = o.p;
@@ -15,7 +15,6 @@ export const PiPo = class PiPo {
   }
 };
 
-// TODO: for animation, moves should contains "moving" and "fading" maybe...
 export const Move = class Move {
   // o: {appear, vanish, [start,] [end,]}
   // appear,vanish = arrays of PiPo
diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue
index b2dd4a72..f4603d6b 100644
--- a/client/src/components/BaseGame.vue
+++ b/client/src/components/BaseGame.vue
@@ -52,7 +52,7 @@ div#baseGame(
         #downloadDiv(v-if="game.vname!='Dark' || game.score!='*'")
           a#download(href="#")
           button(@click="download()") {{ st.tr["Download"] }} PGN
-        button(onClick="doClick('modalAdjust')") &#10530;
+        button(onClick="window.doClick('modalAdjust')") &#10530;
         button(
           v-if="game.vname!='Dark' && game.mode!='analyze'"
           @click="analyzePosition()"
@@ -169,7 +169,6 @@ export default {
   },
   methods: {
     focusBg: function() {
-      // NOTE: small blue border appears...
       document.getElementById("baseGame").focus();
     },
     adjustBoard: function() {
@@ -259,9 +258,9 @@ export default {
         this.game.vname +
         "/?fen=" +
         this.vr.getFen().replace(/ /g, "_");
+      // Open in same tab in live games (against cheating)
       if (this.game.type == "live") this.$router.push(newUrl);
-      //open in same tab: against cheating...
-      else window.open("#" + newUrl); //open in a new tab: more comfortable
+      else window.open("#" + newUrl);
     },
     download: function() {
       const content = this.getPgn();
@@ -305,9 +304,6 @@ export default {
     },
     animateMove: function(move, callback) {
       let startSquare = document.getElementById(getSquareId(move.start));
-      // TODO: error "flush nextTick callbacks" when observer reloads page:
-      // this late check is not a fix!
-      if (!startSquare) return;
       let endSquare = document.getElementById(getSquareId(move.end));
       let rectStart = startSquare.getBoundingClientRect();
       let rectEnd = endSquare.getBoundingClientRect();
@@ -318,9 +314,6 @@ export default {
       let movingPiece = document.querySelector(
         "#" + getSquareId(move.start) + " > img.piece"
       );
-      if (!movingPiece)
-        //TODO: shouldn't happen
-        return;
       // HACK for animation (with positive translate, image slides "under background")
       // Possible improvement: just alter squares on the piece's way...
       const squares = document.getElementsByClassName("board");
@@ -330,7 +323,7 @@ export default {
       }
       movingPiece.style.transform =
         "translate(" + translation.x + "px," + translation.y + "px)";
-      movingPiece.style.transitionDuration = "0.2s";
+      movingPiece.style.transitionDuration = "0.25s";
       movingPiece.style.zIndex = "3000";
       setTimeout(() => {
         for (let i = 0; i < squares.length; i++)
@@ -353,7 +346,8 @@ export default {
         return;
       }
       const doPlayMove = () => {
-        if (!!receive && this.cursor < this.moves.length - 1) this.gotoEnd(); //required to play the move
+        // To play a move, cursor must be at the end of the game:
+        if (!!receive && this.cursor < this.moves.length - 1) this.gotoEnd();
         if (navigate) {
           if (this.cursor == this.moves.length - 1) return; //no more moves
           move = this.moves[this.cursor + 1];
@@ -433,6 +427,7 @@ export default {
 <style lang="sass" scoped>
 [type="checkbox"]#modalEog+div .card
   min-height: 45px
+
 [type="checkbox"]#modalAdjust+div .card
   padding: 5px
 
@@ -454,8 +449,10 @@ export default {
     display: inline-block
     width: 20%
     margin: 0
+
 #turnIndicator
   text-align: center
+
 #belowControls
   border-top: 1px solid #2f4f4f
   text-align: center
@@ -467,13 +464,16 @@ export default {
   & > button
     border-left: 1px solid #2f4f4f
     margin: 0
+
 #boardContainer
   float: left
 // TODO: later, maybe, allow movesList of variable width
 // or e.g. between 250 and 350px (but more complicated)
+
 #movesList
   width: 280px
   float: left
+
 @media screen and (max-width: 767px)
   #movesList
     width: 100%
diff --git a/client/src/components/ChallengeList.vue b/client/src/components/ChallengeList.vue
index 92c42f7c..ed490671 100644
--- a/client/src/components/ChallengeList.vue
+++ b/client/src/components/ChallengeList.vue
@@ -7,7 +7,11 @@ div
         th {{ st.tr["From"] }}
         th {{ st.tr["Cadence"] }}
     tbody
-      tr(v-for="c in sortedChallenges" :class="{toyou:c.priority==1,fromyou:c.priority==2}" @click="$emit('click-challenge',c)")
+      tr(
+        v-for="c in sortedChallenges"
+        :class="{toyou:c.priority==1,fromyou:c.priority==2}"
+        @click="$emit('click-challenge',c)"
+      )
         td {{ c.vname }}
         td {{ c.from.name || "@nonymous" }}
         td {{ c.cadence }}
@@ -47,7 +51,7 @@ export default {
 </script>
 
 <style lang="sass" scoped>
-// TODO: understand why the style applied to <tr> element doesn't work
+// NOTE: the style applied to <tr> element doesn't work
 tr.fromyou > td
   font-style: italic
 tr.toyou > td
diff --git a/client/src/components/Chat.vue b/client/src/components/Chat.vue
index 7205f3b7..f1e1fad2 100644
--- a/client/src/components/Chat.vue
+++ b/client/src/components/Chat.vue
@@ -1,11 +1,17 @@
 <template lang="pug">
 div
-  input#inputChat(type="text" :placeholder="st.tr['Chat here']"
-    @keyup.enter="sendChat()")
+  input#inputChat(
+    type="text"
+    :placeholder="st.tr['Chat here']"
+    @keyup.enter="sendChat()"
+  )
   button(@click="sendChat()") {{ st.tr["Send"] }}
   p(v-for="chat in chats.concat(pastChats)")
     span.name {{ chat.name }} :&nbsp;
-    span(:class="classObject(chat)" v-html="chat.msg")
+    span(
+      :class="classObject(chat)"
+      v-html="chat.msg"
+    )
 </template>
 
 <script>
@@ -53,6 +59,7 @@ export default {
 <style lang="sass" scoped>
 .name
   color: #abb2b9
+
 .my-chatmsg
   color: #7d3c98
 .opp-chatmsg
diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue
index 402bc7fa..4d354713 100644
--- a/client/src/components/ComputerGame.vue
+++ b/client/src/components/ComputerGame.vue
@@ -1,5 +1,10 @@
 <template lang="pug">
-BaseGame(:game="game" :vr="vr" @newmove="processMove" @gameover="gameOver")
+BaseGame(
+  :game="game"
+  :vr="vr"
+  @newmove="processMove"
+  @gameover="gameOver"
+)
 </template>
 
 <script>
@@ -47,7 +52,7 @@ export default {
         this.$emit("game-stopped"); //no more moves: mate or stalemate
         return; //after game ends, no more moves, nothing to do
       }
-      if (!Array.isArray(compMove)) compMove = [compMove]; //to deal with MarseilleRules
+      if (!Array.isArray(compMove)) compMove = [compMove]; //potential multi-move
       // Small delay for the bot to appear "more human"
       const delay = Math.max(500 - (Date.now() - this.timeStart), 0);
       setTimeout(() => {
diff --git a/client/src/components/ContactForm.vue b/client/src/components/ContactForm.vue
index 9c70a559..9046e0a7 100644
--- a/client/src/components/ContactForm.vue
+++ b/client/src/components/ContactForm.vue
@@ -1,7 +1,13 @@
 <template lang="pug">
 div
-  input#modalContact.modal(type="checkbox" @change="trySetEnterTime($event)")
-  div(role="dialog" data-checkbox="modalContact")
+  input#modalContact.modal(
+    type="checkbox"
+    @change="trySetEnterTime($event)"
+  )
+  div(
+    role="dialog"
+    data-checkbox="modalContact"
+  )
     .card
       label.modal-close(for="modalContact")
       form(@submit.prevent="trySendMessage()" @keyup.enter="trySendMessage()")
@@ -56,7 +62,6 @@ export default {
         !confirm(this.st.tr["No subject. Send anyway?"])
       )
         return;
-
       // Message sending:
       ajax(
         "/messages",
@@ -81,9 +86,11 @@ export default {
 [type="checkbox"].modal+div .card
   max-width: 767px
   max-height: 100%
+
 textarea#mailContent
   width: 100%
   min-height: 100px
+
 #dialog
   padding: 5px
   color: blue
diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue
index 7125fde8..77aa83e5 100644
--- a/client/src/components/GameList.vue
+++ b/client/src/components/GameList.vue
@@ -8,12 +8,18 @@ div
         th(v-if="showCadence") {{ st.tr["Cadence"] }}
         th {{ st.tr["Result"] }}
     tbody
-      tr(v-for="g in sortedGames" @click="$emit('show-game',g)"
-          :class="{'my-turn': g.myTurn}")
+      tr(
+        v-for="g in sortedGames"
+        @click="$emit('show-game',g)"
+        :class="{'my-turn': g.myTurn}"
+      )
         td {{ g.vname }}
         td {{ player_s(g) }}
         td(v-if="showCadence") {{ g.cadence }}
-        td(:class="{finished: g.score!='*'}" @click="deleteGame(g,$event)")
+        td(
+          :class="{finished: g.score!='*'}"
+          @click="deleteGame(g,$event)"
+        )
           | {{ g.score }}
 </template>
 
@@ -107,7 +113,7 @@ export default {
 </script>
 
 <style lang="sass" scoped>
-// TODO: understand why the style applied to <tr> element doesn't work
+// NOTE: the style applied to <tr> element doesn't work
 tr.my-turn > td
   background-color: #fcd785
 tr td.finished
diff --git a/client/src/components/Language.vue b/client/src/components/Language.vue
index bdfd3417..633f89dd 100644
--- a/client/src/components/Language.vue
+++ b/client/src/components/Language.vue
@@ -7,7 +7,10 @@ div
       "fr": "Français",
     };
   input#modalLang.modal(type="checkbox")
-  div(role="dialog" data-checkbox="modalLang")
+  div(
+    role="dialog"
+    data-checkbox="modalLang"
+  )
     .card
       label.modal-close(for="modalLang")
       form(@change="setLanguage($event)")
diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue
index 29e9a9fe..aa12969a 100644
--- a/client/src/components/MoveList.vue
+++ b/client/src/components/MoveList.vue
@@ -5,7 +5,7 @@ export default {
   props: ["moves", "cursor", "score", "message", "firstNum"],
   watch: {
     cursor: function(newCursor) {
-      if (window.innerWidth <= 767) return; //moves list is below: scrolling would hide chessboard
+      if (window.innerWidth <= 767) return; //scrolling would hide chessboard
       // Count grouped moves until the cursor (if multi-moves):
       let groupsCount = -1;
       let curCol = undefined;
@@ -119,6 +119,66 @@ export default {
 <style lang="sass" scoped>
 .moves-list
   min-width: 250px
+
 td.highlight-lm
   background-color: plum
 </style>
+
+<!-- TODO: use template function + multi-moves: much easier
+<template lang="pug">
+div
+  #scoreInfo(v-if="score!='*'")
+    p {{ score }}
+    p {{ message }}
+  table.moves-list
+    tbody
+      tr(v-for="moveIdx in evenNumbers")
+        td {{ firstNum + moveIdx / 2 + 1 }}
+        td(:class="{'highlight-lm': cursor == moveIdx}"
+            @click="() => gotoMove(moveIdx)")
+          | {{ moves[moveIdx].notation }}
+        td(v-if="moveIdx < moves.length-1"
+            :class="{'highlight-lm': cursor == moveIdx+1}"
+            @click="() => gotoMove(moveIdx+1)")
+          | {{ moves[moveIdx+1].notation }}
+        // Else: just add an empty cell
+        td(v-else)
+</template>
+
+<script>
+// Component for moves list on the right
+export default {
+  name: 'my-move-list',
+	props: ["moves","cursor","score","message","firstNum"],
+  watch: {
+    cursor: function(newValue) {
+      if (window.innerWidth <= 767)
+        return; //moves list is below: scrolling would hide chessboard
+      if (newValue < 0)
+        newValue = 0; //avoid rows[-1] => error
+      // $nextTick to wait for table > tr to be rendered
+      this.$nextTick( () => {
+        let rows = document.querySelectorAll('#movesList tr');
+        if (rows.length > 0)
+        {
+          rows[Math.floor(newValue/2)].scrollIntoView({
+            behavior: "auto",
+            block: "nearest",
+          });
+        }
+      });
+    },
+  },
+  computed: {
+    evenNumbers: function() {
+      return [...Array(this.moves.length).keys()].filter(i => i%2==0);
+    },
+  },
+  methods: {
+		gotoMove: function(index) {
+			this.$emit("goto-move", index);
+		},
+	},
+};
+</script>
+-->
diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue
index e5a9d9c7..117133d1 100644
--- a/client/src/components/Settings.vue
+++ b/client/src/components/Settings.vue
@@ -1,17 +1,26 @@
 <template lang="pug">
 div
   input#modalSettings.modal(type="checkbox")
-  div(role="dialog" data-checkbox="modalSettings")
+  div(
+    role="dialog"
+    data-checkbox="modalSettings"
+  )
     .card(@change="updateSettings($event)")
       label.modal-close(for="modalSettings")
       form
         fieldset
           label(for="setHints") {{ st.tr["Show possible moves?"] }}
-          input#setHints(type="checkbox" v-model="st.settings.hints")
+          input#setHints(
+            type="checkbox"
+            v-model="st.settings.hints"
+          )
         fieldset
           label(for="setHighlight")
             | {{ st.tr["Highlight last move and checks?"] }}
-          input#setHighlight(type="checkbox" v-model="st.settings.highlight")
+          input#setHighlight(
+            type="checkbox"
+            v-model="st.settings.highlight"
+          )
         fieldset
           label(for="setBcolor") {{ st.tr["Board colors"] }}
           select#setBcolor(v-model="st.settings.bcolor")
diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue
index 222a8625..eb1b6c22 100644
--- a/client/src/components/UpsertUser.vue
+++ b/client/src/components/UpsertUser.vue
@@ -1,7 +1,13 @@
 <template lang="pug">
 div
-  input#modalUser.modal(type="checkbox" @change="trySetEnterTime($event)")
-  div(role="dialog" data-checkbox="modalUser")
+  input#modalUser.modal(
+    type="checkbox"
+    @change="trySetEnterTime($event)"
+  )
+  div(
+    role="dialog"
+    data-checkbox="modalUser"
+  )
     .card
       label.modal-close(for="modalUser")
       h3.section {{ st.tr[stage] }}
@@ -9,23 +15,42 @@ div
         div(v-show="stage!='Login'")
           fieldset
             label(for="username") {{ st.tr["User name"] }}
-            input#username(type="text" v-model="st.user.name")
+            input#username(
+              type="text"
+              v-model="st.user.name"
+            )
           fieldset
             label(for="useremail") {{ st.tr["Email"] }}
-            input#useremail(type="email" v-model="st.user.email")
+            input#useremail(
+              type="email"
+              v-model="st.user.email"
+            )
           fieldset
             label(for="notifyNew") {{ st.tr["Notifications by email"] }}
-            input#notifyNew(type="checkbox" v-model="st.user.notify")
+            input#notifyNew(
+              type="checkbox"
+              v-model="st.user.notify"
+            )
         div(v-show="stage=='Login'")
           fieldset
             label(for="nameOrEmail") {{ st.tr["Name or Email"] }}
-            input#nameOrEmail(type="text" v-model="nameOrEmail")
+            input#nameOrEmail(
+              type="text"
+              v-model="nameOrEmail"
+            )
       .button-group
         button(@click="onSubmit()")
           span {{ st.tr[submitMessage] }}
-        button(v-if="stage!='Update'" type="button" @click="toggleStage()")
+        button(
+          v-if="stage!='Update'"
+          type="button"
+          @click="toggleStage()"
+        )
           span {{ st.tr[stage=="Login" ? "Register" : "Login"] }}
-        button(v-else type="button" @click="doLogout()")
+        button(
+          v-else type="button"
+          @click="doLogout()"
+        )
           span {{ st.tr["Logout"] }}
       #dialog.text-center {{ st.tr[infoMsg] }}
 </template>
@@ -158,6 +183,7 @@ export default {
 [type="checkbox"].modal+div .card
   max-width: 370px
   max-height: 100%
+
 #dialog
   padding: 5px
   color: blue
diff --git a/client/src/data/userCheck.js b/client/src/data/userCheck.js
index dac8ef6f..745d6d6e 100644
--- a/client/src/data/userCheck.js
+++ b/client/src/data/userCheck.js
@@ -3,9 +3,11 @@ export function checkNameEmail(o) {
     if (o.name.length == 0) return "Empty name";
     if (!o.name.match(/^[\w]+$/)) return "Bad characters in name";
   }
+
   if (typeof o.email === "string") {
     if (o.email.length == 0) return "Empty email";
     if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Bad characters in email";
   }
+
   return "";
 }
diff --git a/client/src/store.js b/client/src/store.js
index 373aa288..60e01b97 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -16,9 +16,10 @@ export const store = {
       this.state.variants = res.variantArray;
     });
     let mysid = localStorage.getItem("mysid");
+    // Assign mysid only once (until next time user clear browser data)
     if (!mysid) {
       mysid = getRandString();
-      localStorage.setItem("mysid", mysid); //done only once (unless user clear browser data)
+      localStorage.setItem("mysid", mysid);
     }
     // Quick user setup using local storage:
     this.state.user = {
@@ -34,18 +35,18 @@ export const store = {
       this.state.user.id = res.id;
       const storedId = localStorage.getItem("myid");
       if (res.id > 0 && !storedId)
-        //user cleared localStorage
+        // User cleared localStorage
         localStorage.setItem("myid", res.id);
       else if (res.id == 0 && !!storedId)
-        //user cleared cookie
+        // User cleared cookie
         localStorage.removeItem("myid");
       this.state.user.name = res.name;
       const storedName = localStorage.getItem("myname");
       if (!!res.name && !storedName)
-        //user cleared localStorage
+        // User cleared localStorage
         localStorage.setItem("myname", res.name);
       else if (!res.name && !!storedName)
-        //user cleared cookie
+        // User cleared cookie
         localStorage.removeItem("myname");
       this.state.user.email = res.email;
       this.state.user.notify = res.notify;
diff --git a/client/src/utils/cookie.js b/client/src/utils/cookie.js
index 78551a29..34802243 100644
--- a/client/src/utils/cookie.js
+++ b/client/src/utils/cookie.js
@@ -1,14 +1,14 @@
 // Source: https://www.quirksmode.org/js/cookies.html
 export function setCookie(name, value) {
-  var date = new Date();
+  const date = new Date();
   date.setTime(date.getTime() + 183 * 24 * 60 * 60 * 1000); //6 months
-  var expires = "; expires=" + date.toGMTString();
+  const expires = "; expires=" + date.toGMTString();
   document.cookie = name + "=" + value + expires + "; path=/";
 }
 
 export function getCookie(name, defaut) {
-  var nameEQ = name + "=";
-  var ca = document.cookie.split(";");
+  const nameEQ = name + "=";
+  const ca = document.cookie.split(";");
   for (var i = 0; i < ca.length; i++) {
     var c = ca[i];
     while (c.charAt(0) == " ") c = c.substring(1, c.length);
diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js
index 453388ed..6e9fc818 100644
--- a/client/src/utils/gameStorage.js
+++ b/client/src/utils/gameStorage.js
@@ -6,7 +6,7 @@
 //   players: array of sid+id+name,
 //   cadence: string,
 //   increment: integer (seconds),
-//   mode: string ("live" or "corr")
+//   type: string ("live" or "corr")
 //   imported: boolean (optional, default false)
 //   // Game (dynamic) state:
 //   fen: string,
diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js
index d28f639f..c4e2eb1d 100644
--- a/client/src/variants/Grand.js
+++ b/client/src/variants/Grand.js
@@ -316,7 +316,6 @@ export const VariantRules = class GrandRules extends ChessRules {
     return 2;
   }
 
-  // TODO: this function could be generalized and shared better (how ?!...)
   static GenRandInitFen() {
     let pieces = { w: new Array(10), b: new Array(10) };
     // Shuffle pieces on first and last rank
diff --git a/client/src/variants/Magnetic.js b/client/src/variants/Magnetic.js
index 0110c49e..c2201d60 100644
--- a/client/src/variants/Magnetic.js
+++ b/client/src/variants/Magnetic.js
@@ -20,8 +20,10 @@ export const VariantRules = class MagneticRules extends ChessRules {
   // Complete a move with magnetic actions
   // TODO: job is done multiple times for (normal) promotions.
   applyMagneticLaws(move) {
-    if (move.appear[0].p == V.KING && move.appear.length == 1) return [move]; //kings are not charged
-    const aIdx = move.appear[0].p != V.KING ? 0 : 1; //if castling, rook is charged
+    // Exception: kings are not charged
+    if (move.appear[0].p == V.KING && move.appear.length == 1) return [move];
+    // If castling, rook is charged:
+    const aIdx = move.appear[0].p != V.KING ? 0 : 1;
     const [x, y] = [move.appear[aIdx].x, move.appear[aIdx].y];
     const color = this.turn;
     const lastRank = color == "w" ? 0 : 7;
diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue
index 5d22306e..7259632c 100644
--- a/client/src/views/Analyse.vue
+++ b/client/src/views/Analyse.vue
@@ -3,9 +3,15 @@ main
   .row
     .col-sm-12
       .text-center
-        input#fen(v-model="curFen" @input="adjustFenSize()")
+        input#fen(
+          v-model="curFen"
+          @input="adjustFenSize()"
+        )
         button(@click="gotoFen()") {{ st.tr["Go"] }}
-  BaseGame(:game="game" :vr="vr")
+  BaseGame(
+    :game="game"
+    :vr="vr"
+  )
 </template>
 
 <script>
diff --git a/client/src/views/Auth.vue b/client/src/views/Auth.vue
index a44687c1..36e79c89 100644
--- a/client/src/views/Auth.vue
+++ b/client/src/views/Auth.vue
@@ -2,8 +2,7 @@
 main
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
-      p(:class="{warn:!!this.errmsg}")
-        | {{ errmsg || st.tr["Authentication successful!"] }}
+      p {{ st.tr["Authentication successful!"] }}
 </template>
 
 <script>
@@ -13,8 +12,7 @@ export default {
   name: "my-auth",
   data: function() {
     return {
-      st: store.state,
-      errmsg: ""
+      st: store.state
     };
   },
   created: function() {
@@ -23,25 +21,14 @@ export default {
       "GET",
       { token: this.$route.params["token"] },
       res => {
-        if (!res.errmsg) {
-          //if not already logged in
-          this.st.user.id = res.id;
-          this.st.user.name = res.name;
-          this.st.user.email = res.email;
-          this.st.user.notify = res.notify;
-          localStorage["myname"] = res.name;
-          localStorage["myid"] = res.id;
-        } else this.errmsg = res.errmsg;
+        this.st.user.id = res.id;
+        this.st.user.name = res.name;
+        this.st.user.email = res.email;
+        this.st.user.notify = res.notify;
+        localStorage["myname"] = res.name;
+        localStorage["myid"] = res.id;
       }
     );
   }
 };
 </script>
-
-<style lang="sass" scoped>
-.warn
-  padding: 3px
-  color: red
-  background-color: lightgrey
-  font-weight: bold
-</style>
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index a0e264ed..9a971006 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -1,27 +1,51 @@
 <template lang="pug">
 main
-  input#modalChat.modal(type="checkbox" @click="resetChatColor()")
-  div#chatWrap(role="dialog" data-checkbox="modalChat")
+  input#modalChat.modal(
+    type="checkbox"
+    @click="resetChatColor()"
+  )
+  div#chatWrap(
+    role="dialog"
+    data-checkbox="modalChat"
+  )
     #chat.card
       label.modal-close(for="modalChat")
       #participants
         span {{ Object.keys(people).length + " " + st.tr["participant(s):"] }} 
-        span(v-for="p in Object.values(people)" v-if="!!p.name")
+        span(
+          v-for="p in Object.values(people)"
+          v-if="!!p.name"
+        )
           | {{ p.name }} 
         span.anonymous(v-if="Object.values(people).some(p => !p.name)")
           | + @nonymous
-      Chat(:players="game.players" :pastChats="game.chats"
-        :newChat="newChat" @mychat="processChat")
+      Chat(
+        :players="game.players"
+        :pastChats="game.chats"
+        :newChat="newChat"
+        @mychat="processChat"
+      )
   .row
     #aboveBoard.col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2
       span.variant-cadence {{ game.cadence }}
       span.variant-name {{ game.vname }}
-      button#chatBtn(onClick="doClick('modalChat')") Chat
+      button#chatBtn(onClick="window.doClick('modalChat')") Chat
       #actions(v-if="game.score=='*'")
-        button(@click="clickDraw()" :class="{['draw-' + drawOffer]: true}")
+        button(
+          @click="clickDraw()"
+          :class="{['draw-' + drawOffer]: true}"
+        )
           | {{ st.tr["Draw"] }}
-        button(v-if="!!game.mycolor" @click="abortGame()") {{ st.tr["Abort"] }}
-        button(v-if="!!game.mycolor" @click="resign()") {{ st.tr["Resign"] }}
+        button(
+          v-if="!!game.mycolor"
+          @click="abortGame()"
+        )
+          | {{ st.tr["Abort"] }}
+        button(
+          v-if="!!game.mycolor"
+          @click="resign()"
+        )
+          | {{ st.tr["Resign"] }}
       #playersInfo
         p
           span.name(:class="{connected: isConnected(0)}")
@@ -31,7 +55,12 @@ main
           span.name(:class="{connected: isConnected(1)}")
             | {{ game.players[1].name || "@nonymous" }}
           span.time(v-if="game.score=='*'") {{ virtualClocks[1] }}
-  BaseGame(:game="game" :vr="vr" @newmove="processMove" @gameover="gameOver")
+  BaseGame(
+    :game="game"
+    :vr="vr"
+    @newmove="processMove"
+    @gameover="gameOver"
+  )
 </template>
 
 <script>
@@ -217,7 +246,6 @@ export default {
         case "identity": {
           const user = data.data;
           if (user.name) {
-            //otherwise anonymous
             // If I multi-connect, kill current connexion if no mark (I'm older)
             if (
               this.newConnect[user.sid] &&
@@ -497,6 +525,7 @@ export default {
           }
         }
         if (game.scoreMsg) game.scoreMsg = this.st.tr[game.scoreMsg]; //stored in english
+        delete game["moveToPlay"]; //in case of!
         this.game = Object.assign(
           {},
           game,
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 190717d0..b4869c9c 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -1,20 +1,29 @@
 <template lang="pug">
 main
   input#modalInfo.modal(type="checkbox")
-  div#infoDiv(role="dialog" data-checkbox="modalInfo")
+  div#infoDiv(
+    role="dialog"
+    data-checkbox="modalInfo"
+  )
     .card.text-center
       label.modal-close(for="modalInfo")
       p(v-html="infoMessage")
   input#modalNewgame.modal(type="checkbox")
-  div#newgameDiv(role="dialog" data-checkbox="modalNewgame")
+  div#newgameDiv(
+    role="dialog"
+    data-checkbox="modalNewgame"
+  )
     .card
       label#closeNewgame.modal-close(for="modalNewgame")
       form(@submit.prevent="newChallenge()" @keyup.enter="newChallenge()")
         fieldset
           label(for="selectVariant") {{ st.tr["Variant"] }} *
           select#selectVariant(v-model="newchallenge.vid")
-            option(v-for="v in st.variants" :value="v.id"
-                :selected="newchallenge.vid==v.id")
+            option(
+              v-for="v in st.variants"
+              :value="v.id"
+              :selected="newchallenge.vid==v.id"
+            )
               | {{ v.name }}
         fieldset
           label(for="cadence") {{ st.tr["Cadence"] }} *
@@ -22,34 +31,61 @@ main
             button 3+2
             button 5+3
             button 15+5
-          input#cadence(type="text" v-model="newchallenge.cadence"
-            placeholder="5+0, 1h+30s, 7d+1d ...")
+          input#cadence(
+            type="text"
+            v-model="newchallenge.cadence"
+            placeholder="5+0, 1h+30s, 7d+1d ..."
+          )
         fieldset(v-if="st.user.id > 0")
           label(for="selectPlayers") {{ st.tr["Play with?"] }}
-          input#selectPlayers(type="text" v-model="newchallenge.to")
+          input#selectPlayers(
+            type="text"
+            v-model="newchallenge.to"
+          )
         fieldset(v-if="st.user.id > 0 && newchallenge.to.length > 0")
           label(for="inputFen") FEN
-          input#inputFen(type="text" v-model="newchallenge.fen")
+          input#inputFen(
+            type="text"
+            v-model="newchallenge.fen"
+          )
       button(@click="newChallenge()") {{ st.tr["Send challenge"] }}
-  input#modalPeople.modal(type="checkbox" @click="resetChatColor()")
-  div#peopleWrap(role="dialog" data-checkbox="modalPeople")
+  input#modalPeople.modal(
+    type="checkbox"
+    @click="resetChatColor()"
+  )
+  div#peopleWrap(
+    role="dialog"
+    data-checkbox="modalPeople"
+  )
     .card
       label.modal-close(for="modalPeople")
       #people
         #players
-          p(v-for="sid in Object.keys(people)" v-if="!!people[sid].name")
+          p(
+            v-for="sid in Object.keys(people)"
+            v-if="!!people[sid].name"
+          )
             span {{ people[sid].name }}
-            button.player-action(v-if="sid!=st.user.sid || isGamer(sid)" @click="challOrWatch(sid)")
+            button.player-action(
+              v-if="sid!=st.user.sid || isGamer(sid)"
+              @click="challOrWatch(sid)"
+            )
               | {{ getActionLabel(sid) }}
           p.anonymous @nonymous ({{ anonymousCount }})
         #chat
-          Chat(:newChat="newChat" @mychat="processChat" :pastChats="[]")
+          Chat(
+            :newChat="newChat"
+            @mychat="processChat"
+            :pastChats="[]"
+          )
         .clearer
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       .button-group
-        button#peopleBtn(onClick="doClick('modalPeople')") {{ st.tr["Social"] }}
-        button(onClick="doClick('modalNewgame')") {{ st.tr["New game"] }}
+        button#peopleBtn(onClick="window.doClick('modalPeople')")
+          | {{ st.tr["Social"] }}
+        button(onClick="window.doClick('modalNewgame')")
+          | {{ st.tr["New game"] }}
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       div#div2
@@ -58,20 +94,34 @@ main
             | {{ st.tr["Live challenges"] }}
           button.tabbtn#btnCcorr(@click="setDisplay('c','corr',$event)")
             | {{ st.tr["Correspondance challenges"] }}
-        ChallengeList(v-show="cdisplay=='live'"
-          :challenges="filterChallenges('live')" @click-challenge="clickChallenge")
-        ChallengeList(v-show="cdisplay=='corr'"
-          :challenges="filterChallenges('corr')" @click-challenge="clickChallenge")
+        ChallengeList(
+          v-show="cdisplay=='live'"
+          :challenges="filterChallenges('live')"
+          @click-challenge="clickChallenge"
+        )
+        ChallengeList(
+          v-show="cdisplay=='corr'"
+          :challenges="filterChallenges('corr')"
+          @click-challenge="clickChallenge"
+        )
       div#div3
         .button-group
           button.tabbtn#btnGlive(@click="setDisplay('g','live',$event)")
             | {{ st.tr["Live games"] }}
           button.tabbtn#btnGcorr(@click="setDisplay('g','corr',$event)")
             | {{ st.tr["Correspondance games"] }}
-        GameList(v-show="gdisplay=='live'" :games="filterGames('live')"
-          :showBoth="true" @show-game="showGame")
-        GameList(v-show="gdisplay=='corr'" :games="filterGames('corr')"
-          :showBoth="true" @show-game="showGame")
+        GameList(
+          v-show="gdisplay=='live'"
+          :games="filterGames('live')"
+          :showBoth="true"
+          @show-game="showGame"
+        )
+        GameList(
+          v-show="gdisplay=='corr'"
+          :games="filterGames('corr')"
+          :showBoth="true"
+          @show-game="showGame"
+        )
 </template>
 
 <script>
@@ -417,7 +467,6 @@ export default {
         case "identity": {
           const user = data.data;
           if (user.name) {
-            //otherwise anonymous
             // If I multi-connect, kill current connexion if no mark (I'm older)
             if (
               this.newConnect[user.sid] &&
@@ -775,21 +824,27 @@ div#peopleWrap > .card
   width: 50%
   position: relative
   float: left
+
 #chat
   width: 50%
   float: left
   position: relative
+
 @media screen and (max-width: 767px)
   #players, #chats
     width: 100%
+
 #chat > .card
   max-width: 100%
   margin: 0;
   border: none;
+
 #players > p
   margin-left: 5px
+
 .anonymous
   font-style: italic
+
 button.player-action
   margin-left: 32px
 
diff --git a/client/src/views/Logout.vue b/client/src/views/Logout.vue
index 34dbb49b..62f057c6 100644
--- a/client/src/views/Logout.vue
+++ b/client/src/views/Logout.vue
@@ -2,8 +2,7 @@
 main
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
-      p(:class="{warn:!!this.errmsg}")
-        | {{ errmsg || st.tr["Logout successful!"] }}
+      p {{ st.tr["Logout successful!"] }}
 </template>
 
 <script>
@@ -13,30 +12,22 @@ export default {
   name: "my-logout",
   data: function() {
     return {
-      st: store.state,
-      errmsg: ""
+      st: store.state
     };
   },
   created: function() {
-    // NOTE: this local cleaning would logically happen when we're sure
-    // that token is erased. But in the case a user clear the cookies,
-    // it would lead to situations where he cannot ("locally") log out.
-    // At worst, if token deletion fails the user can erase cookie manually.
-    this.st.user.id = 0;
-    this.st.user.name = "";
-    this.st.user.email = "";
-    this.st.user.notify = false;
-    localStorage.removeItem("myid");
-    localStorage.removeItem("myname");
-    ajax("/logout", "GET"); //TODO: listen for errors?
+    ajax(
+      "/logout",
+      "GET",
+      () => {
+        this.st.user.id = 0;
+        this.st.user.name = "";
+        this.st.user.email = "";
+        this.st.user.notify = false;
+        localStorage.removeItem("myid");
+        localStorage.removeItem("myname");
+      }
+    );
   }
 };
 </script>
-
-<style lang="sass" scoped>
-.warn
-  padding: 3px
-  color: red
-  background-color: lightgrey
-  font-weight: bold
-</style>
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index 01b5264d..22f804dc 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -3,12 +3,20 @@ main
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       .button-group
-        button.tabbtn#liveGames(@click="setDisplay('live',$event)") {{ st.tr["Live games"] }}
-        button.tabbtn#corrGames(@click="setDisplay('corr',$event)") {{ st.tr["Correspondance games"] }}
-      GameList(v-show="display=='live'" :games="liveGames"
-        @show-game="showGame")
-      GameList(v-show="display=='corr'" :games="corrGames"
-        @show-game="showGame")
+        button.tabbtn#liveGames(@click="setDisplay('live',$event)")
+          | {{ st.tr["Live games"] }}
+        button.tabbtn#corrGames(@click="setDisplay('corr',$event)")
+          | {{ st.tr["Correspondance games"] }}
+      GameList(
+        v-show="display=='live'"
+        :games="liveGames"
+        @show-game="showGame"
+      )
+      GameList(
+        v-show="display=='corr'"
+        :games="corrGames"
+        @show-game="showGame"
+      )
 </template>
 
 <script>
diff --git a/client/src/views/News.vue b/client/src/views/News.vue
index 2686552c..ad0561a6 100644
--- a/client/src/views/News.vue
+++ b/client/src/views/News.vue
@@ -1,7 +1,10 @@
 <template lang="pug">
 main
   input#modalNews.modal(type="checkbox")
-  div#newnewsDiv(role="dialog" data-checkbox="modalNews")
+  div#newnewsDiv(
+    role="dialog"
+    data-checkbox="modalNews"
+  )
     .card
       label.modal-close(for="modalNews")
       textarea#newsContent(
@@ -18,13 +21,19 @@ main
         @click="showModalNews"
       )
         | {{ st.tr["Write news"] }}
-      .news(v-for="n,idx in newsList" :class="{margintop:idx>0}")
+      .news(
+        v-for="n,idx in newsList"
+        :class="{margintop:idx>0}"
+      )
         span.ndt {{ formatDatetime(n.added) }}
         div(v-if="devs.includes(st.user.id)")
           button(@click="editNews(n)") {{ st.tr["Edit"] }}
           button(@click="deleteNews(n)") {{ st.tr["Delete"] }}
         p(v-html="parseHtml(n.content)")
-      button(v-if="hasMore" @click="loadMore()")
+      button(
+        v-if="hasMore"
+        @click="loadMore()"
+      )
         | {{ st.tr["Load more"] }}
 </template>
 
@@ -142,27 +151,33 @@ export default {
 [type="checkbox"].modal+div .card
   max-width: 767px
   max-height: 100%
+
 textarea#newsContent
   margin: 0
   width: 100%
   min-height: 200px
   max-height: 100%
+
 #dialog
   padding: 5px
   color: blue
+
 button#writeNews
   margin-top: 0
   margin-bottom: 0
+
 span.ndt
   color: darkblue
   padding: 0 5px 0 var(--universal-margin)
-.margintop
-  margin-top: 25px
-  border-top: 1px solid grey
+
 .news
   padding-top: 10px
   & > div
     display: inline-block
+
+.margintop
+  margin-top: 25px
+  border-top: 1px solid grey
 @media screen and (max-width: 767px)
   .margintop
     margin-top: 10px
diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue
index a9f9fcb6..e17bead7 100644
--- a/client/src/views/Problems.vue
+++ b/client/src/views/Problems.vue
@@ -1,7 +1,13 @@
 <template lang="pug">
 main
-  input#modalNewprob.modal(type="checkbox" @change="infoMsg=''")
-  div#newprobDiv(role="dialog" data-checkbox="modalNewprob")
+  input#modalNewprob.modal(
+    type="checkbox"
+    @change="infoMsg=''"
+  )
+  div#newprobDiv(
+    role="dialog"
+    data-checkbox="modalNewprob"
+  )
     .card(@keyup.enter="sendProblem()")
       label#closeNewprob.modal-close(for="modalNewprob")
       fieldset
@@ -93,7 +99,11 @@ main
           td {{ p.vname }}
           td {{ firstChars(p.instruction) }}
           td {{ p.id }}
-  BaseGame(v-if="showOne" :game="game" :vr="vr")
+  BaseGame(
+    v-if="showOne"
+    :game="game"
+    :vr="vr"
+  )
 </template>
 
 <script>
@@ -310,7 +320,7 @@ export default {
       );
     },
     editProblem: function(prob) {
-      if (!prob.diag) this.setDiagram(prob); //possible because V is loaded at this stage
+      if (!prob.diag) this.setDiagram(prob); //V is loaded at this stage
       this.copyProblem(prob, this.curproblem);
       window.doClick("modalNewprob");
     },
@@ -330,13 +340,17 @@ export default {
 [type="checkbox"].modal+div .card
   max-width: 767px
   max-height: 100%
+
 #inputFen
   width: 100%
+
 textarea
   width: 100%
+
 #diagram
   margin: 0 auto
   max-width: 400px
+
 #controls
   margin: 0
   width: 100%
diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue
index 4daebf9d..55564ffd 100644
--- a/client/src/views/Rules.vue
+++ b/client/src/views/Rules.vue
@@ -4,18 +4,36 @@ main
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       .button-group
         button(@click="clickReadRules()") {{ st.tr["Rules"] }}
-        button(v-show="!gameInProgress" @click="startGame('auto')")
+        button(
+          v-show="!gameInProgress"
+          @click="startGame('auto')"
+        )
           | {{ st.tr["Example game"] }}
-        button(v-show="!gameInProgress" @click="startGame('versus')")
+        button(
+          v-show="!gameInProgress"
+          @click="startGame('versus')"
+        )
           | {{ st.tr["Practice"] }}
-        button(v-show="gameInProgress" @click="stopGame()")
+        button(
+          v-show="gameInProgress"
+          @click="stopGame()"
+        )
           | {{ st.tr["Stop game"] }}
-        button(v-if="display=='rules' && gameInfo.vname!='Dark'"
-            @click="gotoAnalyze()")
+        button(
+          v-if="display=='rules' && gameInfo.vname!='Dark'"
+          @click="gotoAnalyze()"
+        )
           | {{ st.tr["Analyse"] }}
-      div(v-show="display=='rules'" v-html="content")
-  ComputerGame(v-show="display=='computer'" :game-info="gameInfo"
-    @game-over="stopGame" @game-stopped="gameStopped")
+      div(
+        v-show="display=='rules'"
+        v-html="content"
+      )
+  ComputerGame(
+    v-show="display=='computer'"
+    :game-info="gameInfo"
+    @game-over="stopGame"
+    @game-stopped="gameStopped"
+  )
 </template>
 
 <script>
diff --git a/client/src/views/Variants.vue b/client/src/views/Variants.vue
index 31061158..014aca68 100644
--- a/client/src/views/Variants.vue
+++ b/client/src/views/Variants.vue
@@ -2,7 +2,10 @@
 main
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
-      input#prefixFilter(v-model="curPrefix" :placeholder="st.tr['Prefix?']")
+      input#prefixFilter(
+        v-model="curPrefix"
+        :placeholder="st.tr['Prefix?']"
+      )
     .variant.col-sm-12.col-md-5.col-lg-4(
       v-for="(v,idx) in filteredVariants"
       :class="{'col-md-offset-1': idx%2==0, 'col-lg-offset-2': idx%2==0}"
@@ -52,10 +55,10 @@ export default {
 </script>
 
 <style lang="sass" scoped>
-// TODO: box-shadow or box-sizing ? https://stackoverflow.com/a/13517809
 input#prefixFilter
   display: block
   margin: 0 auto
+
 .variant
   box-sizing: border-box
   border: 1px solid brown
diff --git a/server/sockets.js b/server/sockets.js
index 3fb7ea2a..1cc47aeb 100644
--- a/server/sockets.js
+++ b/server/sockets.js
@@ -152,15 +152,22 @@ module.exports = function(wss) {
         case "askfullgame":
         {
           const pg = obj.page || page; //required for askidentity and askgame
-          const tmpIds = Object.keys(clients[pg][obj.target]);
-          if (obj.target == sid) //targetting myself
+          // In cas askfullgame to wrong SID for example, would crash:
+          if (!!clients[pg][obj.target])
           {
-            const idx_myTmpid = tmpIds.findIndex(x => x == tmpId);
-            if (idx_myTmpid >= 0)
-              tmpIds.splice(idx_myTmpid, 1);
+            const tmpIds = Object.keys(clients[pg][obj.target]);
+            if (obj.target == sid) //targetting myself
+            {
+              const idx_myTmpid = tmpIds.findIndex(x => x == tmpId);
+              if (idx_myTmpid >= 0)
+                tmpIds.splice(idx_myTmpid, 1);
+            }
+            const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
+            send(
+              clients[pg][obj.target][tmpIds[tmpId_idx]],
+              {code:obj.code, from:[sid,tmpId,page]}
+            );
           }
-          const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
-          send(clients[pg][obj.target][tmpIds[tmpId_idx]], {code:obj.code, from:[sid,tmpId,page]});
           break;
         }
 
-- 
2.44.0