enforce subprotocol/tableKey to alphanumeric and underscore, refine UI

This commit is contained in:
Iris Lightshard 2023-07-05 22:27:38 -06:00
parent 0d3b4fb5ae
commit 43d85c2abc
Signed by: nilix
GPG key ID: 3B7FBC22144E6398
7 changed files with 132 additions and 78 deletions

View file

@ -16,6 +16,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
)
func apiGetTableList(next http.Handler, udb auth.UserStore) http.Handler {
@ -80,6 +81,13 @@ func apiCreateTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbA
return
}
r := regexp.MustCompile("^[a-zA-Z0-9_]+$")
if !r.MatchString(tableKey.Name) || !r.MatchString(tableKey.Passcode) {
w.WriteHeader(422)
next.ServeHTTP(w, req)
return
}
// table name is primary key so w edon't need to check
err = dbAdapter.CreateTable(tableKey)

View file

@ -127,7 +127,9 @@ async function destroyTable() {
});
if (res.ok) {
conn.close(1000);
initializeMap("");
getTables();
} else {
setErr(await res.json());
}

View file

@ -24,11 +24,9 @@
<button type="submit" id="table_join" onclick="dial();">Join</button>
</form>
</details><br/>
<div id="tabletop" style="display:none;">
<details class="ui_win"><summary>dice</summary>
<details id="dice_win" class="ui_win"><summary>dice</summary>
<select id="num_dice">
<option>1</option>
<option>2</option>

View file

@ -14,6 +14,10 @@ function initializeMap(mapImgUrl) {
mapImg.addTo(map);
map.setMaxBounds([[-180,180],[180,-180]]);
map.setView([0,0], 2);
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
tokens.shift();
}
}
// this works but assumes the map is square (reasonable limitation I think)

View file

@ -14,10 +14,19 @@ function showTableModal(show) {
}
}
function fmtLeading(n) {
return n < 10 ? "0" + n : String(n);
}
function formatDice(r) {
const date = new Date(r.timestamp)
const p = document.createElement("p");
p.innerHTML = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}<br>[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`;
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
p.innerHTML = `${date.getFullYear()}-${fmtLeading(month)}-${fmtLeading(day)} ${fmtLeading(hours)}:${fmtLeading(minutes)}:${fmtLeading(seconds)} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}<br>[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`;
return p;
}
@ -67,58 +76,72 @@ function setMapImg(url) {
initializeMap(url);
}
function isTableKeyValid(name, passcode) {
const r = /^[a-zA-Z0-9_]+$/;
return r.test(name) && r.test(passcode);
}
function dial() {
// get tableKey from UI
const tblNameInput = document.getElementById("input_table_name");
const tblPassInput = document.getElementById("input_table_pass");
if (tblNameInput && tblPassInput && tblNameInput.value && tblPassInput.value) {
tableKey.name = tblNameInput.value;
tableKey.passcode = tblPassInput.value;
if (conn) {
conn.close(1000);
}
conn = new WebSocket(`ws://${location.host}/subscribe`, `${tableKey.name}.${tableKey.passcode}`);
conn.addEventListener("close", e => {
if (e.code > 1001) {
// TODO: add message to let user know they are reconnecting
setTimeout(dial, 1000)
} else {
if (isTableKeyValid(tblNameInput.value, tblPassInput.value)) {
tableKey.name = tblNameInput.value;
tableKey.passcode = tblPassInput.value;
if (conn) {
conn.close(1000);
}
conn = new WebSocket(`ws://${location.host}/subscribe`, `${tableKey.name}.${tableKey.passcode}`);
conn.addEventListener("close", e => {
if (e.code == 1006) {
setErr("Table not found - check the name and passcode are correct");
} else if (e.code > 1001) {
// TODO: add message to let user know they are reconnecting
setErr("Websocket error: trying to reconnect");
setTimeout(dial, 1000)
} else {
tabletop = document.getElementById("tabletop");
if (tabletop) {
tabletop.style.display = "none";
}
table = null;
}
});
conn.addEventListener("open", e => {
// TODO: add message to let user know they are at the table
console.info("socket connected");
tabletop = document.getElementById("tabletop");
if (tabletop) {
tabletop.style.display = "none";
tabletop.style.display = "block";
}
table = null;
}
});
conn.addEventListener("open", e => {
// TODO: add message to let user know they are at the table
console.info("socket connected");
tabletop = document.getElementById("tabletop");
if (tabletop) {
tabletop.style.display = "block";
}
});
conn.addEventListener("error", e => {
setErr(`${e.name}: ${e.message}`);
conn.close(3000);
})
conn.addEventListener("message", e => {
const data = JSON.parse(e.data);
if (table == null) {
// first fetch comes from mongo, so the rolls array in each diceRoll is a byte array and needs to be decoded
data.diceRolls.forEach(r=>{
r.roll = Uint8Array.from(atob(r.roll), c => c.charCodeAt(0))
})
table = data;
makeUpToDate(table);
} else {
makeUpToDate(data);
}
});
conn.addEventListener("error", e => {
setErr(`${e.name}: ${e.message}`);
conn.close(3000);
})
console.log(data);
});
conn.addEventListener("message", e => {
const data = JSON.parse(e.data);
if (table == null) {
// first fetch comes from mongo, so the rolls array in each diceRoll is a byte array and needs to be decoded
data.diceRolls.forEach(r=>{
r.roll = Uint8Array.from(atob(r.roll), c => c.charCodeAt(0))
})
table = data;
makeUpToDate(table);
} else {
makeUpToDate(data);
}
console.log(data);
});
} else {
setErr("Table name and passcode can only be alphanumeric and underscores");
}
}else {
setErr("Table name and passcode required");
}
}
@ -131,4 +154,4 @@ async function publish(msg) {
if (!res.ok) {
setErr("Failed to publish message");
}
}
}

View file

@ -1,3 +1,10 @@
:root {
--bg_color: rgba(0,0,0,0.7);
--fg_color: #ccc;
--main_color: #1f9b92;
--sub_color: #002b36;
}
* {
box-sizing: border-box;
padding: 0;
@ -6,12 +13,12 @@
outline: none;
}
* { scrollbar-color:#1f9b92 #000;}
* { scrollbar-color:var(--main_color) var(--sub_color); }
*::-webkit-scrollbar { width:6px;height:6px; }
*::-webkit-scrollbar-track { background:#000; }
*::-webkit-scrollbar-thumb { background-color:#1f9b92;border-radius:0;border:none; }
*::-webkit-scrollbar-corner { background:#000; }
*::selection { background-color:#1f9b92;color:#000;text-decoration:none;text-shadow:none; }
*::-webkit-scrollbar-track { background: var(--sub_color);}
*::-webkit-scrollbar-thumb { background:var(--main_color);border-radius:0;border:none; }
*::-webkit-scrollbar-corner { background:var(--sub_color); }
*::selection { background-color:var(--main_color);color:var(--bg_color);text-decoration:none;text-shadow:none; }
body {
background: url('./bg.png');
@ -22,15 +29,18 @@ label {
font-size: 80%;
}
input, select {
background: #002b36;
color: #93a1a1;
border: solid 1px gray;
input, select, textarea {
background: var(--sub_color);
color: var(--fg_color);
border: solid 1px transparent;
}
input , select {
margin-right: 1ch;
}
input:active, input:focus, select:active, select:focus {
border: solid 1px cyan;
input:active, input:focus, select:active, select:focus, textarea:focus {
border: solid 1px var(--main_color);
}
@ -41,15 +51,15 @@ ul {
button {
padding: 0.5ch;
background: #000;
color: #fff;
border: solid 2px lightseagreen;
background: transparent;
color: var(--fg_color);
border: solid 2px var(--main_color);
margin-right: 1ch;
}
button:hover {
color: #000;
background: lightseagreen;
color: var(--bg_color);
background: var(--main_color);
}
#errWrapper {
@ -72,8 +82,8 @@ button:hover {
}
#dice_log {
background: #002b36;
color: #93a1a1;
background: var(--sub_color);
color: var(--fg_color);
height: 10em;
max-height: 10em;
display: block;
@ -85,7 +95,7 @@ button:hover {
}
#dice_log p:not(:last-child) {
border-bottom: solid 1px gray;
border-bottom: solid 1px var(--fg_color);
}
#aux {
@ -94,14 +104,12 @@ button:hover {
pre {
font-size: 125%;
background: #002b36;
color: #93a1a1;
background: var(--sub_color);
color: var(--fg_color);
}
#auxMsgZone {
width: 100%;
color: #93a1a1;
background: #002b36;
padding:0.2em;
}
@ -112,18 +120,18 @@ pre {
text-align: left;
position: relative;
margin: 2em;
background: rgba(0,0,0,0.7);
color: #eee;
background: var(--bg_color);
color: var(--fg_color);
display: inline;
height: min-content;
z-index:1;
padding: 0.25em;
border: 2px solid dimgray;;
max-width: 80ch;
border: 2px solid transparent;
max-width: min(60ch, 80vw);
}
.ui_win:hover, .ui_win:active {
border: 2px solid #1f9b92;
.ui_win:focus-within {
border: 2px solid var(--main_color);
}
.ui_win * {
@ -135,7 +143,7 @@ pre {
}
.ui_win a:hover, ui_win a:active {
color: #ff;
color: var(--fg_color);
}
#admin_section {

View file

@ -38,4 +38,15 @@ function loadName() {
}
}
function setupDiceAutoScroll() {
const diceWin = document.getElementById("dice_win");
diceWin.addEventListener("toggle", e => {
if (diceWin.open) {
const diceLog = document.getElementById("dice_log");
diceLog.children[diceLog.children.length - 1].scrollIntoView();
}
});
}
setupDiceAutoScroll();
loadName();