// 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() }