summaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
authorplutorocks <>2026-02-26 21:30:32 +0000
committerplutorocks <>2026-02-26 21:30:32 +0000
commit3db298ec3eca0ed94cb7912f660df7dd1f4582e0 (patch)
treed406e1d64aa10c8027bf4bf4a5914f1378fb495e /client.go
initial commitHEADmaster
Diffstat (limited to 'client.go')
-rw-r--r--client.go166
1 files changed, 166 insertions, 0 deletions
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..f9453b1
--- /dev/null
+++ b/client.go
@@ -0,0 +1,166 @@
+// client.go
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "time"
+ "unicode/utf8"
+
+ "github.com/gorilla/websocket"
+)
+
+type Client struct {
+ hub *Hub
+ conn *websocket.Conn
+ send chan []byte
+ token *TokenConfig
+ nick string
+ serverID string
+ playerUUID string
+ lastSeen time.Time
+}
+
+func (c *Client) readPump() {
+ defer func() {
+ c.hub.unregister(c)
+ c.conn.Close()
+ }()
+
+ c.conn.SetReadLimit(4096)
+ c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
+ c.conn.SetPongHandler(func(string) error {
+ c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
+ return nil
+ })
+
+ for {
+ _, data, err := c.conn.ReadMessage()
+ if err != nil {
+ break
+ }
+
+ var msg IncomingMessage
+ if err := json.Unmarshal(data, &msg); err != nil {
+ log.Printf("invalid json from client: %v", err)
+ continue
+ }
+
+ switch msg.Type {
+ case "chat":
+ c.handleChat(msg)
+ case "location":
+ c.handleLocation(msg)
+ case "nick":
+ c.handleNick(msg)
+ default:
+ log.Printf("unknown message type: %s", msg.Type)
+ }
+ }
+}
+
+func (c *Client) writePump() {
+ ticker := time.NewTicker(30 * time.Second)
+ defer func() {
+ ticker.Stop()
+ c.conn.Close()
+ }()
+
+ for {
+ select {
+ case msg, ok := <-c.send:
+ c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ if !ok {
+ // hub closed the channel
+ c.conn.WriteMessage(websocket.CloseMessage, []byte{})
+ return
+ }
+ if err := c.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
+ return
+ }
+ case <-ticker.C:
+ // ping to keep connection alive
+ c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
+ return
+ }
+ }
+ }
+}
+
+func (c *Client) handleChat(msg IncomingMessage) {
+ if msg.Message == "" {
+ return
+ }
+ if utf8.RuneCountInString(msg.Message) > maxMessageLen {
+ log.Printf("dropping too-long chat message from %s (%s)", c.nick, c.token.Name)
+ return
+ }
+
+ ts := time.Now().Unix()
+ out := OutChat{
+ Type: "chat",
+ FromNick: c.nick,
+ FromInternal: c.token.Name,
+ Message: msg.Message,
+ Timestamp: ts,
+ }
+ data, _ := json.Marshal(out)
+
+ // log event
+ c.hub.addEvent(Event{
+ Type: "chat",
+ Timestamp: ts,
+ FromNick: c.nick,
+ FromInternal: c.token.Name,
+ Message: msg.Message,
+ })
+
+ // GLOBAL broadcast
+ c.hub.broadcastAll(data)
+}
+
+func (c *Client) handleLocation(msg IncomingMessage) {
+ ts := time.Now().Unix()
+ out := OutLocation{
+ Type: "location",
+ Nick: c.nick,
+ Internal: c.token.Name,
+ ServerID: c.serverID,
+ X: msg.X,
+ Y: msg.Y,
+ Z: msg.Z,
+ Dimension: msg.Dimension,
+ Timestamp: ts,
+ }
+ data, _ := json.Marshal(out)
+
+ // log event
+ c.hub.addEvent(Event{
+ Type: "location",
+ Timestamp: ts,
+ FromNick: c.nick,
+ FromInternal: c.token.Name,
+ ServerID: c.serverID,
+ X: msg.X,
+ Y: msg.Y,
+ Z: msg.Z,
+ Dimension: msg.Dimension,
+ })
+
+ // broadcast only to same server
+ c.hub.broadcastToServer(c.serverID, data)
+}
+
+func (c *Client) handleNick(msg IncomingMessage) {
+ if msg.Nick == "" {
+ return
+ }
+ if utf8.RuneCountInString(msg.Nick) > maxNickLen {
+ log.Printf("rejecting nick > %d chars from %s (%s)", maxNickLen, c.nick, c.token.Name)
+ return
+ }
+ c.hub.mu.Lock()
+ c.nick = msg.Nick
+ c.hub.mu.Unlock()
+} \ No newline at end of file