Clean up client API

This commit is contained in:
Christoph Hagen 2021-11-28 23:59:24 +01:00
parent 3a1ef01a54
commit 4b0ca1a2ef
5 changed files with 316 additions and 257 deletions

81
Public/api.js Normal file
View File

@ -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)
}
}

67
Public/elements.js Normal file
View File

@ -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
}

View File

@ -1,76 +1,22 @@
// The web socket to connect to the server // The web socket to connect to the server
var socket = null; var socket = null;
var tableId = "";
function closeSocketIfNeeded() { function closeSocketIfNeeded() {
if (socket) { if (socket) {
socket.close() socket.close()
socket = null didCloseSocket()
} }
} }
function hideLoginWindow() { function didCloseSocket() {
document.getElementById("signup-window").style.display = "none" socket = null
tableId = ""
} }
function showLoginWindow() { function setTableId(table) {
document.getElementById("signup-window").style.display = "table" tableId = 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 showBlankLoginScreen(text) { function showBlankLoginScreen(text) {
@ -82,153 +28,159 @@ function showBlankLoginScreen(text) {
setLoginError(text) setLoginError(text)
} }
async function registerUser() { function showGame(tableId) {
console.log("Registration started") setTableId(tableId)
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() {
const token = getSessionToken() const token = getSessionToken()
if (token) { if (token) {
console.log("Logging out player") openSocket(token)
performLogoutRequest(token) // TODO: Show interface
console.log("Show table " + tableId)
} else { } else {
console.log("No player to log out")
showBlankLoginScreen("") showBlankLoginScreen("")
} }
} }
async function performLogoutRequest(token) { function registerUser() {
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) {
const username = getLoginName() const username = getLoginName()
const password = getLoginPassword() const password = getLoginPassword()
if (username == "") {
console.log("Performing request " + type); setLoginError("Please enter your desired user name")
fetch("/player/" + type + "/" + username, { method: 'POST', body: password }) return
.then(convertServerResponse) }
if (password == "") {
setLoginError("Please enter a password")
return
}
performRegisterPlayerRequest(username, password)
.then(function(token) { .then(function(token) {
setSessionToken(token) setSessionToken(token)
setPlayerName(username) setPlayerName(username)
hideLoginWindow() hideLoginWindow()
setLoginError("") loadCurrentTable(token)
console.log(type + " successful")
performGetPublicTablesList(token)
openSocket(token)
}).catch(function(error) { }).catch(function(error) {
setLoginError(error.message) 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() const token = getSessionToken()
if (token) { if (token) {
console.log("Resuming session with token " + token) performLogoutRequest(token)
resumeSession(token) .then(function() {
showBlankLoginScreen("")
}).catch(function(error) {
showBlankLoginScreen(error.message)
console.log(error)
})
} else { } else {
console.log("No session to resume") showBlankLoginScreen("")
showLoginWindow()
} }
} }
async function resumeSession(token) { function loadExistingSession() {
fetch("/player/resume", { method: 'POST', body: token }) const token = getSessionToken()
.then(convertServerResponse) if (token) {
.then(function(name) { resumeSessionRequest(token)
setPlayerName(name) .then(function(name) {
hideLoginWindow() setPlayerName(name)
console.log("Session resumed") hideLoginWindow()
performGetPublicTablesList(token) loadCurrentTable(token)
openSocket(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) { }).catch(function(error) {
deleteSessionToken() showBlankLoginScreen(error.message)
setLoginError(error.message)
showLoginWindow()
console.log(error)
return
}) })
} }
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 = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/session/start")
socket.onopen = function(e) { socket.onopen = function(e) {
@ -237,14 +189,17 @@ async function openSocket(token) {
socket.onmessage = function(event) { socket.onmessage = function(event) {
// TODO: Handle server data // TODO: Handle server data
//event.data
}; };
socket.onclose = function(event) { socket.onclose = function(event) {
if (event.wasClean) { if (event.wasClean) {
didCloseSocket()
} else { } else {
// e.g. server process killed or network down // e.g. server process killed or network down
// event.code is usually 1006 in this case // 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() { function refreshTables() {
const token = getSessionToken() const token = getSessionToken()
if (token) { if (token) {
performGetPublicTablesList(token) performGetPublicTablesRequest(token)
.then(function(json) {
const html = processTableList(json)
setTableListContent(html)
}).catch(function(error) {
showBlankLoginScreen(error.message)
return
})
} else { } else {
showBlankLoginScreen() 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) { function processTableList(tables) {
var html = "" 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] tableInfo = tables[i]
html += "<div class=\"table-row\">" + html += "<div class=\"table-row\">"
"<button class=\"table-join-btn\" onclick=\"joinTable('" + tableInfo.id + if (tableInfo.players.length < 4) {
"')\">Join</button><div class=\"table-title\">" + tableInfo.name + html += "<button class=\"table-join-btn\" onclick=\"joinTable('" + tableInfo.id + "')\">Join</button>"
"</div><div class=\"table-subtitle\">Players: " + tableInfo.players.join(", ") + "</div></div>" } else {
html += "<button class=\"table-join-btn\" disabled>Full</button>"
}
html += "<div class=\"table-title\">" + tableInfo.name + "</div>"
html += "<div class=\"table-subtitle\">Players: " + tableInfo.players.join(", ") + "</div>"
html += "</div>" // table-row
} }
return html return html
}
function joinTable(tableId) {
const token = getSessionToken()
if (token) {
performJoinTableRequest(tableId, token)
} else {
showBlankLoginScreen()
}
}
async function performJoinTableRequest(tableId, token) {
} }

View File

@ -5,6 +5,9 @@
<title>Schafkopf</title> <title>Schafkopf</title>
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='style.css'> <link rel='stylesheet' type='text/css' media='screen' href='style.css'>
<script src='elements.js'></script>
<script src='api.js'></script>
<script src='game.js'></script>
</head> </head>
<body> <body>
<div id="top-bar"> <div id="top-bar">
@ -43,7 +46,6 @@
</div> </div>
</div> </div>
</div> </div>
<script src='game.js'></script>
<script> <script>
loadExistingSession() loadExistingSession()
</script> </script>

View File

@ -2,6 +2,7 @@
/* Color definitions for light mode */ /* Color definitions for light mode */
--button-color: rgb(255, 172, 39); --button-color: rgb(255, 172, 39);
--button-hover: rgb(255, 185, 72); --button-hover: rgb(255, 185, 72);
--button-disabled: rgb(128, 88, 24);
--button-text: rgb(0,0,0); --button-text: rgb(0,0,0);
--standard-background: rgb(54, 54, 54); --standard-background: rgb(54, 54, 54);
--element-background: rgb(42, 42, 42); --element-background: rgb(42, 42, 42);
@ -27,7 +28,7 @@
input { input {
padding: 8px; padding: 8px;
border: 1px solid var(--element-border); border: 1px solid var(--element-border);
border-radius: 4px; border-radius: 5px;
box-sizing: border-box; box-sizing: border-box;
background-color: var(--standard-background); background-color: var(--standard-background);
} }
@ -101,6 +102,7 @@ body, html {
#player-name { #player-name {
text-align: right; text-align: right;
grid-column: 1; grid-column: 1;
padding-right: 5px;
} }
#logout-button { #logout-button {
@ -125,6 +127,7 @@ body, html {
#table-name-field { #table-name-field {
width: 100%; width: 100%;
font-size: medium;
grid-column: 1; grid-column: 1;
} }
@ -169,7 +172,6 @@ body, html {
grid-template-columns: 100px auto; grid-template-columns: 100px auto;
grid-template-rows: auto auto; grid-template-rows: auto auto;
column-gap: 10px; column-gap: 10px;
/* justify-items: left; */
} }
.table-join-btn { .table-join-btn {
@ -184,7 +186,13 @@ body, html {
grid-row: 1 / span 2; 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); background-color: var(--button-hover);
} }