enforce subprotocol/tableKey to alphanumeric and underscore, refine UI
This commit is contained in:
parent
0d3b4fb5ae
commit
43d85c2abc
7 changed files with 132 additions and 78 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -127,7 +127,9 @@ async function destroyTable() {
|
|||
});
|
||||
if (res.ok) {
|
||||
conn.close(1000);
|
||||
initializeMap("");
|
||||
getTables();
|
||||
|
||||
} else {
|
||||
setErr(await res.json());
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
113
static/socket.js
113
static/socket.js
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
Loading…
Reference in a new issue