connect to websocket using table key as subproto

This commit is contained in:
Iris Lightshard 2023-02-12 08:15:44 -07:00
parent 6fc78c333c
commit 65c3071238
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
5 changed files with 108 additions and 90 deletions

View file

@ -1,7 +1,7 @@
package gametable
import (
"bytes"
"bytes"
"context"
"encoding/json"
"errors"
@ -16,6 +16,7 @@ import (
"log"
"net/http"
"nhooyr.io/websocket"
"strings"
"sync"
"time"
)
@ -56,7 +57,15 @@ func (self *GameTableServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func (self *GameTableServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil)
protocols, err := self.dbAdapter.GetProtocols()
if err != nil {
self.logf("%v", err)
return
}
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: protocols,
})
if err != nil {
self.logf("%v", err)
return
@ -87,14 +96,16 @@ func (self *GameTableServer) subscribe(r *http.Request, c *websocket.Conn) error
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
},
}
tableKey := models.TableKey{}
err := json.NewDecoder(r.Body).Decode(&tableKey)
if err != nil {
fmt.Println(err.Error())
return err
subprotoParts := strings.Split(c.Subprotocol(), ".")
if len(subprotoParts) != 2 {
return errors.New("Couldn't decode subprotocol")
}
tableKey := models.TableKey{
Name: subprotoParts[0],
Passcode: subprotoParts[1],
}
// subprotocol is guaranteed to be an existing table, but maybe just leave this here
if !self.dbAdapter.CheckTable(tableKey) {
return errors.New("Table with matching key was not found on this server")
}
@ -165,12 +176,32 @@ func (self *GameTableServer) publish(msg []byte) {
}
}
func (self *GameTableServer) getCurrentState(tableKey models.TableKey) ([]byte) {
func (self *GameTableServer) getCurrentState(tableKey models.TableKey) []byte {
// get diceroll log, map, and token state
if self.dbAdapter.CheckTable(tableKey) {
mapUrl, _ := self.dbAdapter.GetMapImageUrl(tableKey)
auxMessage, _ := self.dbAdapter.GetAuxMessage(tableKey)
availableTokens, _ := self.dbAdapter.GetTokens(tableKey, true)
activeTokens, _ := self.dbAdapter.GetTokens(tableKey, false)
diceRolls, _ := self.dbAdapter.GetDiceRolls(tableKey)
// build into a []byte message
return make([]byte, 1)
table := models.Table{
Name: tableKey.Name,
Passcode: tableKey.Passcode,
DiceRolls: diceRolls,
MapImageUrl: mapUrl,
Tokens: activeTokens,
AvailableTokens: availableTokens,
AuxMessage: auxMessage,
}
data, err := json.Marshal(table)
if err != nil {
return []byte("{\"error\": \"" + err.Error() + "\"}")
} else {
return data
}
}
return []byte("{\"error\": \"table not found\"}")
}
func (self *GameTableServer) writeToDB(tableMsg models.TableMessage) error {

View file

@ -23,6 +23,7 @@ type DbAdapter interface {
DestroyTable(table models.TableKey) error
CheckTable(table models.TableKey) bool
GetProtocols() ([]string, error)
InsertDiceRoll(table models.TableKey, diceRoll models.DiceRoll) error
GetDiceRolls(table models.TableKey) ([]models.DiceRoll, error)
@ -134,6 +135,30 @@ func (self *DbEngine) CheckTable(table models.TableKey) bool {
return false
}
func (self *DbEngine) GetProtocols() ([]string, error) {
tables := self.db.Collection("tables")
if tables != nil {
var results []models.Table
cursor, err := tables.Find(self.mkCtx(10), bson.D{})
if err != nil {
return []string{}, err
}
if err = cursor.All(self.mkCtx(10), &results); err != nil {
return []string{}, err
}
var protocols []string
for _, t := range results {
protocols = append(protocols, fmt.Sprintf("%s.%s", t.Name, t.Passcode))
}
return protocols, nil
}
return []string{}, errors.New(fmt.Sprintf(errNoCollection, "tables"))
}
func (self *DbEngine) InsertDiceRoll(table models.TableKey, diceRoll models.DiceRoll) error {
tables := self.db.Collection("tables")
if tables != nil {

View file

@ -11,9 +11,14 @@
<div id="errWrapper" style='display:none'><button id="closeErr" onclick="closeErr()">x</button><div id="errDiv"></div></div>
<nav>
<input id="name_entry">
<button id="goto_table">Change Table</button>
<button id="goto_table">Join Table</button>
<button id="admin_login">Admin Login</button>
</nav>
<form id="table_modal" onsubmit="return false">
<label>Table Name<input id="input_table_name"></label>
<label>Table Passcode<input id="input_table_pass"></label>
<button type="submit" id="table_join" onclick="dial()">Join</button>
</form>
<form id="admin_modal" onsubmit="return false">
<label>usr<input id="input_admin_usr"></label>
<label>pass<input type="password" id="input_admin_pass"></label>
@ -67,6 +72,6 @@
</div>
</body>
<script src="./util.js" type="text/javascript"></script>
<script src="./socket.js" type="text/javascript"></script>
<script src="./admin.js" type="text/javascript"></script>
<script src="./index.js" type="text/javascript"></script>
</html>

View file

@ -1,76 +0,0 @@
;(() => {
// expectingMessage is set to true
// if the user has just submitted a message
// and so we should scroll the next message into view when received.
let expectingMessage = false
function dial() {
const conn = new WebSocket(`ws://${location.host}/subscribe`)
conn.addEventListener("close", ev => {
appendLog(`WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`, true)
if (ev.code !== 1001) {
appendLog("Reconnecting in 1s", true)
setTimeout(dial, 1000)
}
})
conn.addEventListener("open", ev => {
console.info("websocket connected")
})
// This is where we handle messages received.
conn.addEventListener("message", ev => {
if (typeof ev.data !== "string") {
console.error("unexpected message type", typeof ev.data)
return
}
const p = appendLog(ev.data)
if (expectingMessage) {
p.scrollIntoView()
expectingMessage = false
}
})
}
dial()
const messageLog = document.getElementById("message-log")
const publishForm = document.getElementById("publish-form")
const messageInput = document.getElementById("message-input")
// appendLog appends the passed text to messageLog.
function appendLog(text, error) {
const p = document.createElement("p")
// Adding a timestamp to each message makes the log easier to read.
p.innerText = `${new Date().toLocaleTimeString()}: ${text}`
if (error) {
p.style.color = "red"
p.style.fontStyle = "bold"
}
messageLog.append(p)
return p
}
appendLog("Submit a message to get started!")
// onsubmit publishes the message from the user when the form is submitted.
publishForm.onsubmit = async ev => {
ev.preventDefault()
const msg = messageInput.value
if (msg === "") {
return
}
messageInput.value = ""
expectingMessage = true
try {
const resp = await fetch("/publish", {
method: "POST",
body: msg,
})
if (resp.status !== 202) {
throw new Error(`Unexpected HTTP Status ${resp.status} ${resp.statusText}`)
}
} catch (err) {
appendLog(`Publish failed: ${err.message}`, true)
}
}
})()

33
static/socket.js Normal file
View file

@ -0,0 +1,33 @@
let tableKey = {
name: "",
passcode: ""
}
let table = null;
let conn = null;
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;
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)
}
});
conn.addEventListener("open", e => {
// TODO: add message to let user know they are at the table
console.info("socket connected");
});
conn.addEventListener("message", e => {
console.dir(e);
});
}
}