From 4b0ca1a2ef3bf7abcdcf059e615ef80718ab79a1 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 28 Nov 2021 23:59:24 +0100 Subject: [PATCH] Clean up client API --- Public/api.js | 81 +++++++++ Public/elements.js | 67 +++++++ Public/game.js | 407 ++++++++++++++++-------------------------- Public/schafkopf.html | 4 +- Public/style.css | 14 +- 5 files changed, 316 insertions(+), 257 deletions(-) create mode 100644 Public/api.js create mode 100644 Public/elements.js diff --git a/Public/api.js b/Public/api.js new file mode 100644 index 0000000..43b06dd --- /dev/null +++ b/Public/api.js @@ -0,0 +1,81 @@ +/** +* This file acts as an abstraction layer to the server. +*/ + +async function performRegisterPlayerRequest(name, password){ + return fetch("/player/register/" + name, { method: 'POST', body: password }) + .then(convertServerResponse) +} + +async function performDeletePlayerRequest(name, password){ + return fetch("/player/delete/" + name, { method: 'POST', body: password }) + .then(convertServerResponse) + .then(function(value) {}) +} + +async function performLoginPlayerRequest(name, password) { + return fetch("/player/login/" + name, { method: 'POST', body: password }) + .then(convertServerResponse) +} + +async function performLogoutRequest(token) { + return fetch("/player/logout", { method: 'POST', body: token }) + .then(convertServerResponse) + .then(function(value) {}) +} + +async function resumeSessionRequest(token) { + return fetch("/player/resume", { method: 'POST', body: token }) + .then(convertServerResponse) +} + +async function performGetCurrentTableRequest(token) { + return fetch("player/table", { method: 'POST', body: token }) + .then(convertServerResponse) +} + +async function performCreateTableRequest(token, name, visibility) { + const vis = visibility ? "public" : "private"; + return fetch("/table/create/" + vis + "/" + name, { method: 'POST', body: token }) + .then(convertServerResponse) +} + +async function performJoinTableRequest(tableId, token) { + return fetch("/table/join/" + tableId, { method: 'POST', body: token }) + .then(convertServerResponse) + .then(function(value) {}) +} + +async function performGetPublicTablesRequest(token) { + return fetch("/tables/public", { method: 'POST', body: token }) + .then(convertServerResponse) + .then(function(text) { + const decoded = atob(text) + return JSON.parse(decoded); + }) +} + +function convertServerResponse(response) { + switch (response.status) { + case 200: // Success + return response.text() + case 400: // Bad request + throw Error("The request was malformed") + case 401: // Unauthorized (Invalid session token) + throw Error("Please log in again") + case 403: // Forbidden + throw Error("Invalid username or password") + case 410: // Gone + throw Error("The table could not be found") + case 406: // notAcceptable + throw Error("The password or name is too long") + case 409: // Conflict + throw Error("A user with the same name is already registered") + case 417: // Expectation failed + throw Error("The table is already full and can't be joined") + case 424: // Failed dependency + throw Error("The request couldn't be completed") + default: + throw Error("Unexpected response: " + response.statusText) + } +} \ No newline at end of file diff --git a/Public/elements.js b/Public/elements.js new file mode 100644 index 0000000..0455703 --- /dev/null +++ b/Public/elements.js @@ -0,0 +1,67 @@ +/** + * This file acts as an abstraction layer between HTML and JS. + */ + +function hideLoginWindow() { + document.getElementById("signup-window").style.display = "none" +} + +function showLoginWindow() { + document.getElementById("signup-window").style.display = "table" +} + +function setPlayerName(name) { + document.getElementById("player-name").innerHTML = name +} + +function getPlayerName() { + return document.getElementById("player-name").innerHTML +} + +function getLoginName() { + return document.getElementById("user-name").value +} + +function clearLoginName() { + document.getElementById("user-name").value = "" +} + +function getLoginPassword() { + return document.getElementById("user-pwd").value +} + +function clearLoginPassword() { + document.getElementById("user-pwd").value = "" +} + +function getSessionToken() { + return localStorage.getItem('token') +} + +function setSessionToken(token) { + localStorage.setItem('token', token) +} + +function deleteSessionToken() { + localStorage.removeItem('token') +} + +function setLoginError(text) { + document.getElementById("login-error").innerHTML = text +} + +function getTableName() { + return document.getElementById("table-name-field").value +} + +function clearTableName() { + return document.getElementById("table-name-field").value = "" +} + +function getTableVisibility() { + return document.getElementById("table-public-checkbox").checked +} + +function setTableListContent(content) { + document.getElementById("table-list").innerHTML = content +} diff --git a/Public/game.js b/Public/game.js index 3eae3fc..7bc02d7 100644 --- a/Public/game.js +++ b/Public/game.js @@ -1,76 +1,22 @@ // The web socket to connect to the server var socket = null; +var tableId = ""; function closeSocketIfNeeded() { if (socket) { socket.close() - socket = null + didCloseSocket() } } -function hideLoginWindow() { - document.getElementById("signup-window").style.display = "none" +function didCloseSocket() { + socket = null + tableId = "" } -function showLoginWindow() { - document.getElementById("signup-window").style.display = "table" -} - -function setPlayerName(name) { - document.getElementById("player-name").innerHTML = name -} - -function getPlayerName() { - return document.getElementById("player-name").innerHTML -} - -function getLoginName() { - return document.getElementById("user-name").value -} - -function clearLoginName() { - document.getElementById("user-name").value = "" -} - -function getLoginPassword() { - return document.getElementById("user-pwd").value -} - -function clearLoginPassword() { - document.getElementById("user-pwd").value = "" -} - -function getSessionToken() { - return localStorage.getItem('token') -} - -function setSessionToken(token) { - localStorage.setItem('token', token) -} - -function deleteSessionToken() { - localStorage.removeItem('token') -} - -function setLoginError(text) { - document.getElementById("login-error").innerHTML = text -} - -function getTableName() { - return document.getElementById("table-name-field").value -} - -function clearTableName() { - return document.getElementById("table-name-field").value = "" -} - -function getTableVisibility() { - return document.getElementById("table-public-checkbox").checked -} - -function setTableListContent(content) { - document.getElementById("table-list").innerHTML = content +function setTableId(table) { + tableId = table } function showBlankLoginScreen(text) { @@ -82,153 +28,159 @@ function showBlankLoginScreen(text) { setLoginError(text) } -async function registerUser() { - console.log("Registration started") - performGetSessionTokenRequest("register") -} - -async function deletePlayerAccount() { - const name = getPlayerName() - const password = getLoginPassword() - - fetch("/player/delete/" + username, { method: 'POST', body: password }) - .then(function(response) { - if (response.status == 200) { // Success - return - } - if (response.status == 400) { // Bad request - throw Error("The request had an error") - } - if (response.status == 401) { // Invalid session token - throw Error("Please log in again") - } - if (response.status == 403) { // Forbidden - throw Error("The password or name is incorrect") - } - if (response.status == 424) { // Failed dependency - throw Error("The request couldn't be completed") - } - throw Error("Unexpected registration response: " + response.statusText) - }).then(function() { - showBlankLoginScreen("") - console.log("Player deleted") - }).catch(function(error) { - closeSocketIfNeeded() - deleteSessionToken() - alert(error.message) - console.log(error) - }) -} - -async function loginUser() { - console.log("Login started"); - performGetSessionTokenRequest("login") -} - -async function logoutUser() { +function showGame(tableId) { + setTableId(tableId) const token = getSessionToken() if (token) { - console.log("Logging out player") - performLogoutRequest(token) + openSocket(token) + // TODO: Show interface + console.log("Show table " + tableId) } else { - console.log("No player to log out") showBlankLoginScreen("") } } -async function performLogoutRequest(token) { - fetch("/player/logout", { method: 'POST', body: token }) - .then(function(response) { - if (response.status == 200) { // Success - return - } - if (response.status == 400) { // Bad request - throw Error("The request had an error") - } - throw Error("Unexpected logout response: " + response.statusText) - }).then(function() { - showBlankLoginScreen("") - console.log("Player logged out") - }).catch(function(error) { - showBlankLoginScreen(error.message) - console.log(error) - }) -} - -function convertServerResponse(response) { - if (response.status == 200) { // Success - return response.text() - } - if (response.status == 400) { // Bad request - throw Error("The request was malformed") - } - if (response.status == 403) { // Forbidden - throw Error("Invalid username or password") - } - if (response.status == 406) { // notAcceptable - throw Error("The password or name is too long") - } - if (response.status == 409) { // Conflict - throw Error("A user with the same name is already registered") - } - if (response.status == 424) { // Failed dependency - throw Error("The request couldn't be completed") - } - throw Error("Unexpected response: " + response.statusText) -} - -async function performGetSessionTokenRequest(type) { +function registerUser() { const username = getLoginName() const password = getLoginPassword() - - console.log("Performing request " + type); - fetch("/player/" + type + "/" + username, { method: 'POST', body: password }) - .then(convertServerResponse) + if (username == "") { + setLoginError("Please enter your desired user name") + return + } + if (password == "") { + setLoginError("Please enter a password") + return + } + performRegisterPlayerRequest(username, password) .then(function(token) { setSessionToken(token) setPlayerName(username) hideLoginWindow() - setLoginError("") - console.log(type + " successful") - performGetPublicTablesList(token) - openSocket(token) + loadCurrentTable(token) }).catch(function(error) { setLoginError(error.message) - console.log(error) - return }) } -async function loadExistingSession() { +function deletePlayerAccount() { + const name = getPlayerName() + const password = getLoginPassword() + + performDeletePlayerRequest(name, password, function(error) { + if (error == "") { + showBlankLoginScreen("") + console.log("Player deleted") + } else { + closeSocketIfNeeded() + deleteSessionToken() + alert(error) + console.log(error) + } + }) +} + +function loginUser() { + const username = getLoginName() + const password = getLoginPassword() + if (username == "") { + setLoginError("Please enter your user name") + return + } + if (password == "") { + setLoginError("Please enter your password") + return + } + performLoginPlayerRequest(username, password) + .then(function(token) { + setSessionToken(token) + setPlayerName(username) + hideLoginWindow() + loadCurrentTable(token) + }).catch(function(error) { + setLoginError(error.message) + }) +} + +function logoutUser() { const token = getSessionToken() if (token) { - console.log("Resuming session with token " + token) - resumeSession(token) + performLogoutRequest(token) + .then(function() { + showBlankLoginScreen("") + }).catch(function(error) { + showBlankLoginScreen(error.message) + console.log(error) + }) } else { - console.log("No session to resume") - showLoginWindow() + showBlankLoginScreen("") } } -async function resumeSession(token) { - fetch("/player/resume", { method: 'POST', body: token }) - .then(convertServerResponse) - .then(function(name) { - setPlayerName(name) - hideLoginWindow() - console.log("Session resumed") - performGetPublicTablesList(token) - openSocket(token) +function loadExistingSession() { + const token = getSessionToken() + if (token) { + resumeSessionRequest(token) + .then(function(name) { + setPlayerName(name) + hideLoginWindow() + loadCurrentTable(token) + }).catch(function(error) { + showBlankLoginScreen(error.message) + }) + } else { + showBlankLoginScreen("") + } +} + +function loadCurrentTable(token) { + performGetCurrentTableRequest(token) + .then(function(tableId) { + if (tableId == "") { + refreshTables() + return + } + showGame(tableId) }).catch(function(error) { - deleteSessionToken() - setLoginError(error.message) - showLoginWindow() - console.log(error) - return + showBlankLoginScreen(error.message) }) } -async function openSocket(token) { +function createTable() { + const tableName = getTableName() + if (tableName == "") { + return + } + const token = getSessionToken() + if (token) { + const isVisible = getTableVisibility() + performCreateTableRequest(token, tableName, isVisible) + .then(function(tableId) { + clearTableName() + showGame(tableId) + }).catch(function(error) { + showBlankLoginScreen(error.message) + }) + } else { + showBlankLoginScreen("") + } +} + +function joinTable(tableId) { + const token = getSessionToken() + if (token) { + performJoinTableRequest(tableId, token) + .then(function() { + showGame(tableId) + }) + .catch(function(error) { + showBlankLoginScreen(error.message) + }) + } else { + showBlankLoginScreen("") + } +} + +function openSocket(token) { socket = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/session/start") socket.onopen = function(e) { @@ -237,14 +189,17 @@ async function openSocket(token) { socket.onmessage = function(event) { // TODO: Handle server data + //event.data }; socket.onclose = function(event) { if (event.wasClean) { - + didCloseSocket() } else { // e.g. server process killed or network down // event.code is usually 1006 in this case + // TODO: Retry several times to open socket, + // stop after too many failed attempts } }; @@ -253,89 +208,35 @@ async function openSocket(token) { }; } -function createTable() { - const tableName = getTableName() - const isVisible = getTableVisibility() - const token = getSessionToken() - if (token) { - performCreateTableRequest(token, tableName, isVisible) - } -} - -async function performCreateTableRequest(token, name, visibility) { - const vis = visibility ? "public" : "private"; - fetch("/table/create/" + vis + "/" + name, { method: 'POST', body: token }) - .then(function(response) { - if (response.status == 200) { // Success - return response.text() - } - if (response.status == 400) { // Bad request - throw Error("The request had an error") - } - if (response.status == 401) { // Token invalid - showBlankLoginScreen("") - return "" - } - throw Error("Unexpected registration response: " + response.statusText) - }) - .then(function(tableId) { - if (tableId == "") { - return; - } - clearTableName() - console.log("Created table " + tableId) - }).catch(function(error) { - showBlankLoginScreen(error.message) - console.log(error) - return - }) -} - function refreshTables() { const token = getSessionToken() if (token) { - performGetPublicTablesList(token) + performGetPublicTablesRequest(token) + .then(function(json) { + const html = processTableList(json) + setTableListContent(html) + }).catch(function(error) { + showBlankLoginScreen(error.message) + return + }) } else { showBlankLoginScreen() } } -async function performGetPublicTablesList(token) { - fetch("/tables/public", { method: 'POST', body: token }) - .then(convertServerResponse) - .then(function(text) { - const decoded = atob(text) - const json = JSON.parse(decoded); - const html = processTableList(json) - setTableListContent(html) - }).catch(function(error) { - showBlankLoginScreen(error.message) - console.log(error) - return - }) -} - function processTableList(tables) { var html = "" - for (let i = 0, len = tables.length, text = ""; i < len; i++) { + for (let i = 0, len = tables.length; i < len; i++) { tableInfo = tables[i] - html += "
" + - "
" + tableInfo.name + - "
Players: " + tableInfo.players.join(", ") + "
" + html += "
" + if (tableInfo.players.length < 4) { + html += "" + } else { + html += "" + } + html += "
" + tableInfo.name + "
" + html += "
Players: " + tableInfo.players.join(", ") + "
" + html += "
" // table-row } return html -} - -function joinTable(tableId) { - const token = getSessionToken() - if (token) { - performJoinTableRequest(tableId, token) - } else { - showBlankLoginScreen() - } -} - -async function performJoinTableRequest(tableId, token) { - } \ No newline at end of file diff --git a/Public/schafkopf.html b/Public/schafkopf.html index 94ea18b..7cf9fb8 100644 --- a/Public/schafkopf.html +++ b/Public/schafkopf.html @@ -5,6 +5,9 @@ Schafkopf + + +
@@ -43,7 +46,6 @@
- diff --git a/Public/style.css b/Public/style.css index 64c3562..3f3426e 100644 --- a/Public/style.css +++ b/Public/style.css @@ -2,6 +2,7 @@ /* Color definitions for light mode */ --button-color: rgb(255, 172, 39); --button-hover: rgb(255, 185, 72); + --button-disabled: rgb(128, 88, 24); --button-text: rgb(0,0,0); --standard-background: rgb(54, 54, 54); --element-background: rgb(42, 42, 42); @@ -27,7 +28,7 @@ input { padding: 8px; border: 1px solid var(--element-border); - border-radius: 4px; + border-radius: 5px; box-sizing: border-box; background-color: var(--standard-background); } @@ -101,6 +102,7 @@ body, html { #player-name { text-align: right; grid-column: 1; + padding-right: 5px; } #logout-button { @@ -125,6 +127,7 @@ body, html { #table-name-field { width: 100%; + font-size: medium; grid-column: 1; } @@ -169,7 +172,6 @@ body, html { grid-template-columns: 100px auto; grid-template-rows: auto auto; column-gap: 10px; - /* justify-items: left; */ } .table-join-btn { @@ -184,7 +186,13 @@ body, html { grid-row: 1 / span 2; } -.table-join-btn:hover { +.table-join-btn:disabled, +.table-join-btn[disabled] { + background-color: var(--button-disabled); + cursor: not-allowed; +} + +.table-join-btn:hover:enabled { background-color: var(--button-hover); }