From e57c4de4148d43e7635e09adcde4e56585aea303 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sun, 8 Mar 2020 11:59:11 +0100
Subject: [PATCH] Replaced AJAX by fetch: not everything tested yet, but seems
 fine

---
 client/src/components/ContactForm.vue |  21 ++--
 client/src/components/GameList.vue    |  10 +-
 client/src/components/UpsertUser.vue  |  33 +++---
 client/src/parameters.js.dist         |   3 -
 client/src/store.js                   |  14 ++-
 client/src/utils/ajax.js              |  78 ++++++-------
 client/src/views/Auth.vue             |  21 ++--
 client/src/views/Game.vue             |  42 ++++---
 client/src/views/Hall.vue             | 161 ++++++++++++++++----------
 client/src/views/Logout.vue           |  19 +--
 client/src/views/MyGames.vue          |  37 +++---
 client/src/views/News.vue             |  96 +++++++++------
 client/src/views/Problems.vue         | 112 +++++++++++-------
 client/src/views/Rules.vue            |  22 ++--
 server/app.js                         |  20 +++-
 server/routes/all.js                  |   5 -
 server/routes/problems.js             |   2 +-
 server/routes/users.js                |   2 +-
 server/routes/variants.js             |   2 +-
 server/while_update/favicon.ico       |   1 +
 server/while_update/index.html        |  27 +++++
 21 files changed, 440 insertions(+), 288 deletions(-)
 create mode 100644 server/while_update/favicon.ico
 create mode 100644 server/while_update/index.html

diff --git a/client/src/components/ContactForm.vue b/client/src/components/ContactForm.vue
index f9c5c60b..50270e69 100644
--- a/client/src/components/ContactForm.vue
+++ b/client/src/components/ContactForm.vue
@@ -23,7 +23,7 @@ div
 </template>
 
 <script>
-import { ajax } from "../utils/ajax";
+import { ajax } from "@/utils/ajax";
 import { store } from "@/store";
 import { checkNameEmail } from "@/data/userCheck";
 export default {
@@ -66,14 +66,17 @@ export default {
         "/messages",
         "POST",
         {
-          email: email.value,
-          subject: subject.value,
-          content: content.value
-        },
-        () => {
-          this.infoMsg = "Email sent!";
-          subject.value = "";
-          content.value = "";
+          nocredentials: true,
+          data: {
+            email: email.value,
+            subject: subject.value,
+            content: content.value
+          },
+          success: () => {
+            this.infoMsg = "Email sent!";
+            subject.value = "";
+            content.value = "";
+          }
         }
       );
     }
diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue
index 098efd06..451d1bac 100644
--- a/client/src/components/GameList.vue
+++ b/client/src/components/GameList.vue
@@ -168,10 +168,12 @@ export default {
               "/games",
               "PUT",
               {
-                gid: game.id,
-                newObj: { removeFlag: true }
-              },
-              afterDelete
+                data: {
+                  gid: game.id,
+                  newObj: { removeFlag: true }
+                },
+                success: afterDelete
+              }
             );
           }
         }
diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue
index 2be637c6..ca322e0c 100644
--- a/client/src/components/UpsertUser.vue
+++ b/client/src/components/UpsertUser.vue
@@ -165,21 +165,26 @@ export default {
       ajax(
         this.ajaxUrl(),
         this.ajaxMethod(),
-        this.stage == "Login"
-          ? { nameOrEmail: this.nameOrEmail }
-          : this.user,
-        () => {
-          this.infoMsg = this.infoMessage();
-          if (this.stage != "Update") this.nameOrEmail = "";
-          else {
-            this.st.user.name = this.user.name;
-            this.st.user.email = this.user.email;
-            this.st.user.notify = this.user.notify;
+        {
+          credentials: true,
+          data: (
+            this.stage == "Login"
+              ? { nameOrEmail: this.nameOrEmail }
+              : this.user
+          ),
+          success: () => {
+            this.infoMsg = this.infoMessage();
+            if (this.stage != "Update") this.nameOrEmail = "";
+            else {
+              this.st.user.name = this.user.name;
+              this.st.user.email = this.user.email;
+              this.st.user.notify = this.user.notify;
+            }
+          },
+          error: (err) => {
+            this.infoMsg = "";
+            alert(err);
           }
-        },
-        err => {
-          this.infoMsg = "";
-          alert(err);
         }
       );
     },
diff --git a/client/src/parameters.js.dist b/client/src/parameters.js.dist
index e96535b0..2e47407e 100644
--- a/client/src/parameters.js.dist
+++ b/client/src/parameters.js.dist
@@ -6,9 +6,6 @@ const Parameters =
   // URL of the server (leave blank for 1-server case)
   serverUrl: "http://localhost:3000",
 
-  // true if the server is at a different address
-  cors: true,
-
   // "include" if the server is at a different address
   credentials: "same-origin"
 };
diff --git a/client/src/store.js b/client/src/store.js
index c98657ef..668f999d 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -13,12 +13,23 @@ export const store = {
   },
   socketCloseListener: null,
   initialize() {
+    const headers = {
+      "Content-Type": "application/json;charset=UTF-8",
+      "X-Requested-With": "XMLHttpRequest"
+    };
     fetch(
       params.serverUrl + "/variants",
-      {method: "GET"},
+      {
+        method: "GET",
+        headers: headers
+      }
     )
     .then(res => res.json())
     .then(json => {
+      if (!Array.isArray(json.variantArray)) {
+        alert("Variants loading failed: reload the page");
+        return;
+      }
       this.state.variants = json.variantArray.sort(
         (v1,v2) => v1.name.localeCompare(v2.name));
     });
@@ -42,6 +53,7 @@ export const store = {
       params.serverUrl + "/whoami",
       {
         method: "GET",
+        headers: headers,
         credentials: params.credentials
       }
     )
diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js
index 4573dd85..e43e9094 100644
--- a/client/src/utils/ajax.js
+++ b/client/src/utils/ajax.js
@@ -1,9 +1,6 @@
 import params from "../parameters"; //for server URL
 import { store } from "../store"; //for translations
 
-// TODO: replace by fetch API ?
-// https://www.sitepoint.com/xmlhttprequest-vs-the-fetch-api-whats-best-for-ajax-in-2019/
-
 // From JSON (encoded string values!) to "arg1=...&arg2=..."
 function toQueryString(data) {
   let data_str = "";
@@ -13,49 +10,44 @@ function toQueryString(data) {
   return data_str.slice(0, -1); //remove last "&"
 }
 
-// data, error: optional
-export function ajax(url, method, data, success, error) {
-  let xhr = new XMLHttpRequest();
-  if (data === undefined || typeof data === "function") {
-    //no data
-    error = success;
-    success = data;
-    data = {};
-  }
-  if (!success) success = () => {}; //by default, do nothing
-  if (!error)
-    error = errmsg => {
+// TODO: use this syntax https://stackoverflow.com/a/29823632 ?
+// data, success, error: optional
+export function ajax(url, method, options) {
+  const data = options.data || {};
+  // By default, do nothing on success and print errors:
+  if (!options.success)
+    options.success = () => {};
+  if (!options.error) {
+    options.error = (errmsg) => {
       alert(store.state.tr[errmsg] || errmsg);
     };
-  xhr.onreadystatechange = function() {
-    if (this.readyState == 4 && this.status == 200) {
-      let res_json = "";
-      try {
-        res_json = JSON.parse(xhr.responseText);
-      } catch (e) {
-        // Plain text (e.g. for rules retrieval) (TODO: no more plain text in current version)
-        success(xhr.responseText);
-      }
-      if (res_json) {
-        if (!res_json.errmsg && !res_json.errno) success(res_json);
-        else {
-          if (res_json.errmsg) error(res_json.errmsg);
-          else error(res_json.code + ". errno = " + res_json.errno);
-        }
-      }
-    }
-  };
-
-  if (["GET", "DELETE"].includes(method) && !!data) {
+  }
+  if (["GET", "DELETE"].includes(method) && !!data)
     // Append query params to URL
     url += "/?" + toQueryString(data);
+  const headers = {
+    "Content-Type": "application/json;charset=UTF-8",
+    "X-Requested-With": "XMLHttpRequest"
+  };
+  let fetchOptions = {
+    method: method,
+    headers: headers,
+  };
+  if (["POST", "PUT"].includes(method))
+    fetchOptions["body"] = JSON.stringify(data);
+  if (
+    !!options.credentials ||
+    (method != "GET" && !options.nocredentials)
+  ) {
+    fetchOptions["credentials"] = params.credentials;
   }
-  xhr.open(method, params.serverUrl + url, true);
-  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-  // Next line to allow cross-domain cookies in dev mode
-  if (params.cors) xhr.withCredentials = true;
-  if (["POST", "PUT"].includes(method)) {
-    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
-    xhr.send(JSON.stringify(data));
-  } else xhr.send();
+  fetch(params.serverUrl + url, fetchOptions)
+  .then(res => res.json())
+  .then(json => {
+    if (!json.errmsg && !json.errno) options.success(json);
+    else {
+      if (!!json.errmsg) options.error(json.errmsg);
+      else options.error(json.code + ". errno = " + json.errno);
+    }
+  });
 }
diff --git a/client/src/views/Auth.vue b/client/src/views/Auth.vue
index 0c5afe9c..b28ac1ef 100644
--- a/client/src/views/Auth.vue
+++ b/client/src/views/Auth.vue
@@ -21,15 +21,18 @@ export default {
     ajax(
       "/authenticate",
       "GET",
-      { token: this.$route.params["token"] },
-      res => {
-        this.authOk = true;
-        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;
+      {
+        credentials: true,
+        data: { token: this.$route.params["token"] },
+        success: (res) => {
+          this.authOk = true;
+          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;
+        }
       }
     );
   }
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index 4ebc8f4c..7f1bd407 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -331,8 +331,13 @@ export default {
     clearChat: function() {
       // Nothing more to do if game is live (chats not recorded)
       if (this.game.type == "corr") {
-        if (!!this.game.mycolor)
-          ajax("/chats", "DELETE", {gid: this.game.id});
+        if (!!this.game.mycolor) {
+          ajax(
+            "/chats",
+            "DELETE",
+            { data: { gid: this.game.id } }
+          );
+        }
         this.$set(this.game, "chats", []);
       }
     },
@@ -639,11 +644,13 @@ export default {
         "/games",
         "PUT",
         {
-          gid: this.gameRef.id,
-          newObj: obj
-        },
-        () => {
-          if (!!callback) callback();
+          data: {
+            gid: this.gameRef.id,
+            newObj: obj
+          },
+          success: () => {
+            if (!!callback) callback();
+          }
         }
       );
     },
@@ -865,13 +872,20 @@ export default {
         const gid = this.gameRef.id;
         if (Number.isInteger(gid) || !isNaN(parseInt(gid))) {
           // corr games identifiers are integers
-          ajax("/games", "GET", { gid: gid }, res => {
-            let g = res.game;
-            g.moves.forEach(m => {
-              m.squares = JSON.parse(m.squares);
-            });
-            afterRetrieval(g);
-          });
+          ajax(
+            "/games",
+            "GET",
+            {
+              data: { gid: gid },
+              success: (res) => {
+                let g = res.game;
+                g.moves.forEach(m => {
+                  m.squares = JSON.parse(m.squares);
+                });
+                afterRetrieval(g);
+              }
+            }
+          );
         }
         else
           // Local game
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 009a1abe..5e7a10d4 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -238,61 +238,72 @@ export default {
     ajax(
       "/games",
       "GET",
-      { uid: this.st.user.id, excluded: true },
-      response => {
-        this.games = this.games.concat(
-          response.games.map(g => {
-            const type = this.classifyObject(g);
-            const vname = this.getVname(g.vid);
-            return Object.assign({}, g, { type: type, vname: vname });
-          })
-        );
+      {
+        data: { uid: this.st.user.id, excluded: true },
+        success: (response) => {
+          this.games = this.games.concat(
+            response.games.map(g => {
+              const type = this.classifyObject(g);
+              const vname = this.getVname(g.vid);
+              return Object.assign({}, g, { type: type, vname: vname });
+            })
+          );
+        }
       }
     );
     // Also ask for corr challenges (open + sent by/to me)
-    ajax("/challenges", "GET", { uid: this.st.user.id }, response => {
-      // Gather all senders names, and then retrieve full identity:
-      // (TODO [perf]: some might be online...)
-      let names = {};
-      response.challenges.forEach(c => {
-        if (c.uid != this.st.user.id) names[c.uid] = "";
-        else if (!!c.target && c.target != this.st.user.id)
-          names[c.target] = "";
-      });
-      const addChallenges = () => {
-        names[this.st.user.id] = this.st.user.name; //in case of
-        this.challenges = this.challenges.concat(
-          response.challenges.map(c => {
-            const from = { name: names[c.uid], id: c.uid }; //or just name
-            const type = this.classifyObject(c);
-            const vname = this.getVname(c.vid);
-            return Object.assign(
-              {},
+    ajax(
+      "/challenges",
+      "GET",
+      {
+        data: { uid: this.st.user.id },
+        success: (response) => {
+          // Gather all senders names, and then retrieve full identity:
+          // (TODO [perf]: some might be online...)
+          let names = {};
+          response.challenges.forEach(c => {
+            if (c.uid != this.st.user.id) names[c.uid] = "";
+            else if (!!c.target && c.target != this.st.user.id)
+              names[c.target] = "";
+          });
+          const addChallenges = () => {
+            names[this.st.user.id] = this.st.user.name; //in case of
+            this.challenges = this.challenges.concat(
+              response.challenges.map(c => {
+                const from = { name: names[c.uid], id: c.uid }; //or just name
+                const type = this.classifyObject(c);
+                const vname = this.getVname(c.vid);
+                return Object.assign(
+                  {},
+                  {
+                    type: type,
+                    vname: vname,
+                    from: from,
+                    to: c.target ? names[c.target] : ""
+                  },
+                  c
+                );
+              })
+            );
+          };
+          if (Object.keys(names).length > 0) {
+            ajax(
+              "/users",
+              "GET",
               {
-                type: type,
-                vname: vname,
-                from: from,
-                to: c.target ? names[c.target] : ""
-              },
-              c
+                data: { ids: Object.keys(names).join(",") },
+                success: (response2) => {
+                  response2.users.forEach(u => {
+                    names[u.id] = u.name;
+                  });
+                  addChallenges();
+                }
+              }
             );
-          })
-        );
-      };
-      if (Object.keys(names).length > 0) {
-        ajax(
-          "/users",
-          "GET",
-          { ids: Object.keys(names).join(",") },
-          response2 => {
-            response2.users.forEach(u => {
-              names[u.id] = u.name;
-            });
-            addChallenges();
-          }
-        );
-      } else addChallenges();
-    });
+          } else addChallenges();
+        }
+      }
+    );
     const connectAndPoll = () => {
       this.send("connect");
       this.send("pollclientsandgamers");
@@ -751,7 +762,11 @@ export default {
           // Delete current challenge (will be replaced now)
           this.send("deletechallenge", { data: this.challenges[cIdx].id });
           if (ctype == "corr") {
-            ajax("/challenges", "DELETE", { id: this.challenges[cIdx].id });
+            ajax(
+              "/challenges",
+              "DELETE",
+              { data: { id: this.challenges[cIdx].id } }
+            );
           }
           this.challenges.splice(cIdx, 1);
         }
@@ -788,9 +803,16 @@ export default {
         finishAddChallenge(null);
       } else {
         // Correspondance game: send challenge to server
-        ajax("/challenges", "POST", { chall: chall }, response => {
-          finishAddChallenge(response.cid);
-        });
+        ajax(
+          "/challenges",
+          "POST",
+          {
+            data: { chall: chall },
+            success: (response) => {
+              finishAddChallenge(response.cid);
+            }
+          }
+        );
       }
     },
     // Callback function after a diagram was showed to accept
@@ -813,8 +835,13 @@ export default {
         const oppsid = this.getOppsid(c);
         if (!!oppsid)
           this.send("refusechallenge", { data: c.id, target: oppsid });
-        if (c.type == "corr")
-          ajax("/challenges", "DELETE", { id: c.id });
+        if (c.type == "corr") {
+          ajax(
+            "/challenges",
+            "DELETE",
+            { data: { id: c.id } }
+          );
+        }
       }
       this.send("deletechallenge", { data: c.id });
     },
@@ -853,8 +880,13 @@ export default {
       }
       else {
         // My challenge
-        if (c.type == "corr")
-          ajax("/challenges", "DELETE", { id: c.id });
+        if (c.type == "corr") {
+          ajax(
+            "/challenges",
+            "DELETE",
+            { data: { id: c.id } }
+          );
+        }
         this.send("deletechallenge", { data: c.id });
       }
       // In all cases, the challenge is consumed:
@@ -889,11 +921,14 @@ export default {
         ajax(
           "/games",
           "POST",
-          { gameInfo: gameInfo, cid: c.id }, //cid useful to delete challenge
-          response => {
-            gameInfo.id = response.gameId;
-            notifyNewgame();
-            this.$router.push("/game/" + response.gameId);
+          {
+            // cid is useful to delete the challenge:
+            data: { gameInfo: gameInfo, cid: c.id },
+            success: (response) => {
+              gameInfo.id = response.gameId;
+              notifyNewgame();
+              this.$router.push("/game/" + response.gameId);
+            }
           }
         );
       }
diff --git a/client/src/views/Logout.vue b/client/src/views/Logout.vue
index 0db08a93..919e4929 100644
--- a/client/src/views/Logout.vue
+++ b/client/src/views/Logout.vue
@@ -21,14 +21,17 @@ export default {
     ajax(
       "/logout",
       "GET",
-      () => {
-        this.logoutOk = true;
-        this.st.user.id = 0;
-        this.st.user.name = "";
-        this.st.user.email = "";
-        this.st.user.notify = false;
-        localStorage.removeItem("myid");
-        localStorage.removeItem("myname");
+      {
+        credentials: true,
+        success: () => {
+          this.logoutOk = true;
+          this.st.user.id = 0;
+          this.st.user.name = "";
+          this.st.user.email = "";
+          this.st.user.notify = false;
+          localStorage.removeItem("myid");
+          localStorage.removeItem("myname");
+        }
       }
     );
   }
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index 2701e9ef..5148c3cf 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -53,18 +53,21 @@ export default {
       ajax(
         "/games",
         "GET",
-        { uid: this.st.user.id },
-        res => {
-          let serverGames = res.games.filter(g => {
-            const mySide =
-              g.players[0].uid == this.st.user.id
-                ? "White"
-                : "Black";
-            return !g["deletedBy" + mySide];
-          });
-          serverGames.forEach(g => g.type = "corr");
-          this.corrGames = serverGames;
-      });
+        {
+          data: { uid: this.st.user.id },
+          success: (res) => {
+            let serverGames = res.games.filter(g => {
+              const mySide =
+                g.players[0].uid == this.st.user.id
+                  ? "White"
+                  : "Black";
+              return !g["deletedBy" + mySide];
+            });
+            serverGames.forEach(g => g.type = "corr");
+            this.corrGames = serverGames;
+          }
+        }
+      );
     }
     // Initialize connection
     this.connexionString =
@@ -157,10 +160,12 @@ export default {
           "/games",
           "PUT",
           {
-            gid: game.id,
-            newObj: {
-              score: "?",
-              scoreMsg: getScoreMessage("?")
+            data: {
+              gid: game.id,
+              newObj: {
+                score: "?",
+                scoreMsg: getScoreMessage("?")
+              }
             }
           }
         );
diff --git a/client/src/views/News.vue b/client/src/views/News.vue
index f9e8adcd..1aebbd34 100644
--- a/client/src/views/News.vue
+++ b/client/src/views/News.vue
@@ -56,11 +56,18 @@ export default {
     };
   },
   created: function() {
-    ajax("/news", "GET", { cursor: this.cursor }, res => {
-      this.newsList = res.newsList.sort((n1, n2) => n2.added - n1.added);
-      const L = res.newsList.length;
-      if (L > 0) this.cursor = this.newsList[0].id;
-    });
+    ajax(
+      "/news",
+      "GET",
+      {
+        data: { cursor: this.cursor },
+        success: (res) => {
+          this.newsList = res.newsList.sort((n1, n2) => n2.added - n1.added);
+          const L = res.newsList.length;
+          if (L > 0) this.cursor = this.newsList[0].id;
+        }
+      }
+    );
   },
   mounted: function() {
     document
@@ -99,23 +106,30 @@ export default {
     sendNews: function() {
       const edit = this.curnews.id > 0;
       this.infoMsg = "Processing... Please wait";
-      ajax("/news", edit ? "PUT" : "POST", { news: this.curnews }, res => {
-        if (edit) {
-          let n = this.newsList.find(n => n.id == this.curnews.id);
-          if (n) n.content = this.curnews.content;
-        } else {
-          const newNews = {
-            content: this.curnews.content,
-            added: Date.now(),
-            uid: this.st.user.id,
-            id: res.id
-          };
-          this.newsList = [newNews].concat(this.newsList);
+      ajax(
+        "/news",
+        edit ? "PUT" : "POST",
+        {
+          data: { news: this.curnews },
+          success: (res) => {
+            if (edit) {
+              let n = this.newsList.find(n => n.id == this.curnews.id);
+              if (n) n.content = this.curnews.content;
+            } else {
+              const newNews = {
+                content: this.curnews.content,
+                added: Date.now(),
+                uid: this.st.user.id,
+                id: res.id
+              };
+              this.newsList = [newNews].concat(this.newsList);
+            }
+            document.getElementById("modalNews").checked = false;
+            this.infoMsg = "";
+            this.resetCurnews();
+          }
         }
-        document.getElementById("modalNews").checked = false;
-        this.infoMsg = "";
-        this.resetCurnews();
-      });
+      );
     },
     editNews: function(n) {
       this.curnews.content = n.content;
@@ -126,22 +140,36 @@ export default {
     deleteNews: function(n) {
       if (confirm(this.st.tr["Are you sure?"])) {
         this.infoMsg = "Processing... Please wait";
-        ajax("/news", "DELETE", { id: n.id }, () => {
-          const nIdx = this.newsList.findIndex(nw => nw.id == n.id);
-          this.newsList.splice(nIdx, 1);
-          this.infoMsg = "";
-          document.getElementById("modalNews").checked = false;
-        });
+        ajax(
+          "/news",
+          "DELETE",
+          {
+            data: { id: n.id },
+            success: () => {
+              const nIdx = this.newsList.findIndex(nw => nw.id == n.id);
+              this.newsList.splice(nIdx, 1);
+              this.infoMsg = "";
+              document.getElementById("modalNews").checked = false;
+            }
+          }
+        );
       }
     },
     loadMore: function() {
-      ajax("/news", "GET", { cursor: this.cursor }, res => {
-        if (res.newsList.length > 0) {
-          this.newsList = this.newsList.concat(res.newsList);
-          const L = res.newsList.length;
-          if (L > 0) this.cursor = res.newsList[L - 1].id;
-        } else this.hasMore = false;
-      });
+      ajax(
+        "/news",
+        "GET",
+        {
+          data: { cursor: this.cursor },
+          success: (res) => {
+            if (res.newsList.length > 0) {
+              this.newsList = this.newsList.concat(res.newsList);
+              const L = res.newsList.length;
+              if (L > 0) this.cursor = res.newsList[L - 1].id;
+            } else this.hasMore = false;
+          }
+        }
+      );
     }
   }
 };
diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue
index a1c3e83c..ef05e121 100644
--- a/client/src/views/Problems.vue
+++ b/client/src/views/Problems.vue
@@ -145,34 +145,47 @@ export default {
     };
   },
   created: function() {
-    ajax("/problems", "GET", res => {
-      // Show newest problem first:
-      this.problems = res.problems.sort((p1, p2) => p2.added - p1.added);
-      if (this.st.variants.length > 0)
-        this.problems.forEach(p => this.setVname(p));
-      // Retrieve all problems' authors' names
-      let names = {};
-      this.problems.forEach(p => {
-        if (p.uid != this.st.user.id) names[p.uid] = "";
-        else p.uname = this.st.user.name;
-      });
-      const showOneIfPid = () => {
-        const pid = this.$route.query["id"];
-        if (pid) this.showProblem(this.problems.find(p => p.id == pid));
-      };
-      if (Object.keys(names).length > 0) {
-        ajax("/users", "GET", { ids: Object.keys(names).join(",") }, res2 => {
-          res2.users.forEach(u => {
-            names[u.id] = u.name;
-          });
+    ajax(
+      "/problems",
+      "GET",
+      {
+        success: (res) => {
+          // Show newest problem first:
+          this.problems = res.problems.sort((p1, p2) => p2.added - p1.added);
+          if (this.st.variants.length > 0)
+            this.problems.forEach(p => this.setVname(p));
+          // Retrieve all problems' authors' names
+          let names = {};
           this.problems.forEach(p => {
-            if (!p.uname)
-              p.uname = names[p.uid];
+            if (p.uid != this.st.user.id) names[p.uid] = "";
+            else p.uname = this.st.user.name;
           });
-          showOneIfPid();
-        });
-      } else showOneIfPid();
-    });
+          const showOneIfPid = () => {
+            const pid = this.$route.query["id"];
+            if (pid) this.showProblem(this.problems.find(p => p.id == pid));
+          };
+          if (Object.keys(names).length > 0) {
+            ajax(
+              "/users",
+              "GET",
+              {
+                data: { ids: Object.keys(names).join(",") },
+                success: (res2) => {
+                  res2.users.forEach(u => {
+                    names[u.id] = u.name;
+                  });
+                  this.problems.forEach(p => {
+                    if (!p.uname)
+                      p.uname = names[p.uid];
+                  });
+                  showOneIfPid();
+                }
+              }
+            );
+          } else showOneIfPid();
+        }
+      }
+    );
   },
   mounted: function() {
     document
@@ -322,22 +335,24 @@ export default {
       ajax(
         "/problems",
         edit ? "PUT" : "POST",
-        { prob: this.curproblem },
-        ret => {
-          if (edit) {
-            let editedP = this.problems.find(p => p.id == this.curproblem.id);
-            this.copyProblem(this.curproblem, editedP);
-            this.showProblem(editedP);
+        {
+          data: { prob: this.curproblem },
+          success: (ret) => {
+            if (edit) {
+              let editedP = this.problems.find(p => p.id == this.curproblem.id);
+              this.copyProblem(this.curproblem, editedP);
+              this.showProblem(editedP);
+            }
+            else {
+              let newProblem = Object.assign({}, this.curproblem);
+              newProblem.id = ret.id;
+              newProblem.uid = this.st.user.id;
+              newProblem.uname = this.st.user.name;
+              this.problems = [newProblem].concat(this.problems);
+            }
+            document.getElementById("modalNewprob").checked = false;
+            this.infoMsg = "";
           }
-          else {
-            let newProblem = Object.assign({}, this.curproblem);
-            newProblem.id = ret.id;
-            newProblem.uid = this.st.user.id;
-            newProblem.uname = this.st.user.name;
-            this.problems = [newProblem].concat(this.problems);
-          }
-          document.getElementById("modalNewprob").checked = false;
-          this.infoMsg = "";
         }
       );
     },
@@ -349,10 +364,17 @@ export default {
     },
     deleteProblem: function(prob) {
       if (confirm(this.st.tr["Are you sure?"])) {
-        ajax("/problems", "DELETE", { id: prob.id }, () => {
-          ArrayFun.remove(this.problems, p => p.id == prob.id);
-          this.backToList();
-        });
+        ajax(
+          "/problems",
+          "DELETE",
+          {
+            data: { id: prob.id },
+            success: () => {
+              ArrayFun.remove(this.problems, p => p.id == prob.id);
+              this.backToList();
+            }
+          }
+        );
       }
     }
   }
diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue
index 1851609f..7830d26a 100644
--- a/client/src/views/Rules.vue
+++ b/client/src/views/Rules.vue
@@ -78,17 +78,17 @@ export default {
       if (!this.gameInfo.vname) return ""; //variant not set yet
       // (AJAX) Request to get rules content (plain text, HTML)
       return (
-        require("raw-loader!@/translations/rules/" +
-          this.gameInfo.vname +
-          "/" +
-          this.st.lang +
-          ".pug")
-          // Next two lines fix a weird issue after last update (2019-11)
-          .replace(/\\n/g, " ")
-          .replace(/\\"/g, '"')
-          .replace('module.exports = "', "")
-          .replace(/"$/, "")
-          .replace(/(fen:)([^:]*):/g, this.replaceByDiag)
+        require(
+          "raw-loader!@/translations/rules/" +
+          this.gameInfo.vname + "/" +
+          this.st.lang + ".pug"
+        )
+        // Next two lines fix a weird issue after last update (2019-11)
+        .replace(/\\n/g, " ")
+        .replace(/\\"/g, '"')
+        .replace('module.exports = "', "")
+        .replace(/"$/, "")
+        .replace(/(fen:)([^:]*):/g, this.replaceByDiag)
       );
     }
   },
diff --git a/server/app.js b/server/app.js
index eced7d33..d029c956 100644
--- a/server/app.js
+++ b/server/app.js
@@ -28,7 +28,10 @@ else
 app.use(express.json());
 app.use(express.urlencoded({ extended: false }));
 app.use(cookieParser());
-app.use(express.static(path.join(__dirname, 'static'))); //client "prod" files
+// Client "prod" files:
+app.use(express.static(path.join(__dirname, 'static')));
+// Update in progress:
+app.use(express.static(path.join(__dirname, 'while_update')));
 
 // In development stage the client side has its own server
 if (params.cors.enable)
@@ -36,9 +39,14 @@ if (params.cors.enable)
   app.use(function(req, res, next) {
     res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
     res.header("Access-Control-Allow-Credentials", true); //for cookies
-    res.header("Access-Control-Allow-Headers",
-      "Origin, X-Requested-With, Content-Type, Accept");
-    res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
+    res.header(
+      "Access-Control-Allow-Headers",
+      "Origin, X-Requested-With, Content-Type, Accept"
+    );
+    res.header(
+      "Access-Control-Allow-Methods",
+      "GET, POST, OPTIONS, PUT, DELETE"
+    );
     next();
   });
 }
@@ -47,12 +55,12 @@ if (params.cors.enable)
 const routes = require(path.join(__dirname, "routes", "all"));
 app.use('/', routes);
 
-// catch 404 and forward to error handler
+// Catch 404 and forward to error handler
 app.use(function(req, res, next) {
   next(createError(404));
 });
 
-// error handler
+// Error handler
 app.use(function(err, req, res, next) {
   res.status(err.status || 500);
   if (app.get('env') === 'development')
diff --git a/server/routes/all.js b/server/routes/all.js
index b58638e4..0c02129f 100644
--- a/server/routes/all.js
+++ b/server/routes/all.js
@@ -1,11 +1,6 @@
 let router = require("express").Router();
 const access = require("../utils/access");
 
-// To avoid a weird preflight AJAX request error in dev mode...
-router.get("/", access.ajax, (req,res) => {
-  res.json({});
-});
-
 router.use("/", require("./challenges"));
 router.use("/", require("./games"));
 router.use("/", require("./messages"));
diff --git a/server/routes/problems.js b/server/routes/problems.js
index 64c173a1..732ea710 100644
--- a/server/routes/problems.js
+++ b/server/routes/problems.js
@@ -22,7 +22,7 @@ router.post("/problems", access.logged, access.ajax, (req,res) => {
     res.json({});
 });
 
-router.get("/problems", (req,res) => {
+router.get("/problems", access.ajax, (req,res) => {
   const probId = req.query["pid"];
   if (probId && probId.match(/^[0-9]+$/))
   {
diff --git a/server/routes/users.js b/server/routes/users.js
index 11966754..2b38c37f 100644
--- a/server/routes/users.js
+++ b/server/routes/users.js
@@ -34,7 +34,7 @@ router.post('/register', access.unlogged, access.ajax, (req,res) => {
 });
 
 // NOTE: this method is safe because the sessionToken must be guessed
-router.get("/whoami", (req,res) => {
+router.get("/whoami", access.ajax, (req,res) => {
   const callback = (user) => {
     res.json({
       name: user.name,
diff --git a/server/routes/variants.js b/server/routes/variants.js
index 57d4a30b..3f7417e3 100644
--- a/server/routes/variants.js
+++ b/server/routes/variants.js
@@ -4,7 +4,7 @@ let router = require("express").Router();
 const VariantModel = require("../models/Variant");
 const access = require("../utils/access");
 
-router.get('/variants', function(req, res) {
+router.get('/variants', access.ajax, function(req, res) {
   VariantModel.getAll((err,variants) => {
     res.json(err || {variantArray:variants});
   });
diff --git a/server/while_update/favicon.ico b/server/while_update/favicon.ico
new file mode 100644
index 00000000..7b26be14
--- /dev/null
+++ b/server/while_update/favicon.ico
@@ -0,0 +1 @@
+#$# git-fat 79e7b6b353fa77bdb2ab20fcb86055db27175b91                 9662
diff --git a/server/while_update/index.html b/server/while_update/index.html
new file mode 100644
index 00000000..bee7392a
--- /dev/null
+++ b/server/while_update/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>vchess - club</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <style>
+      #container {
+        position: absolute;
+        width: 100%;
+        top: 30%;
+      }
+      p {
+        text-align: center;
+        font-weight: bold;
+        font-size: 2rem;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="container">
+      <p>Website update in progress!</p>
+      <p>It won't be long. Don't worry :)</p>
+    </div>
+  </body>
+</html>
-- 
2.44.0