From 65c30712382e1da8757cdaabafa725b42770ed59 Mon Sep 17 00:00:00 2001 From: Derek Stevens Date: Sun, 12 Feb 2023 08:15:44 -0700 Subject: [PATCH] connect to websocket using table key as subproto --- gametable/server.go | 55 +++++++++++++++++++++++++------- mongodb/adapter.go | 25 +++++++++++++++ static/index.html | 9 ++++-- static/index.js | 76 --------------------------------------------- static/socket.js | 33 ++++++++++++++++++++ 5 files changed, 108 insertions(+), 90 deletions(-) delete mode 100644 static/index.js create mode 100644 static/socket.js diff --git a/gametable/server.go b/gametable/server.go index 8b579e9..ed4a728 100644 --- a/gametable/server.go +++ b/gametable/server.go @@ -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 { diff --git a/mongodb/adapter.go b/mongodb/adapter.go index e02262c..650cbf9 100644 --- a/mongodb/adapter.go +++ b/mongodb/adapter.go @@ -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 { diff --git a/static/index.html b/static/index.html index ef49402..6c50547 100644 --- a/static/index.html +++ b/static/index.html @@ -11,9 +11,14 @@ +
+ + + +
@@ -67,6 +72,6 @@ + - \ No newline at end of file diff --git a/static/index.js b/static/index.js deleted file mode 100644 index 15e2e4a..0000000 --- a/static/index.js +++ /dev/null @@ -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) - } - } -})() \ No newline at end of file diff --git a/static/socket.js b/static/socket.js new file mode 100644 index 0000000..e95a1e5 --- /dev/null +++ b/static/socket.js @@ -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); + }); + } +} \ No newline at end of file