package main // WebSocket client read/write pumps. // // ReadPump pulls messages from the client's WebSocket connection and routes // them into the Hub. WritePump pushes outbound messages from the Hub to the // client's connection. Both functions are expected to run as goroutines per // client. import ( "encoding/json" "fmt" "radchat/server" "time" "github.com/gorilla/websocket" ) // ReadPump reads from the websocket connection and handles client pings/pongs. func ReadPump(c *server.Client) { defer func() { c.Hub.Unregister <- c err := c.Conn.Close() if err != nil { return } }() for { var msg Message err := c.Conn.ReadJSON(&msg) if err != nil { break } switch msg.Type { case "system_message": messageContent := msg.Data.(string) messageQualifier := msg.DataExt.(string) messageUserList := make(map[string]bool) for _, v := range msg.DataList { if v == nil { continue } messageUserList[v.(string)] = true } messageType := msg.DataType messageTimeout := msg.DataTime var messageJson []byte if messageTimeout == 0 { messageJson, _ = json.Marshal(Message{ Type: "system_message", Data: messageContent, DataType: messageType, }) } else { messageJson, _ = json.Marshal(Message{ Type: "system_message", Data: messageContent, DataType: messageType, DataTime: messageTimeout, }) } switch messageQualifier { case "all": for cli := range c.Hub.Clients { SendToClient(c.Hub, cli.Id, messageJson) } case "include": for cli := range c.Hub.Clients { if _, ok := messageUserList[cli.Id]; ok { SendToClient(c.Hub, cli.Id, messageJson) } } case "except": for cli := range c.Hub.Clients { if _, ok := messageUserList[cli.Id]; !ok { SendToClient(c.Hub, cli.Id, messageJson) } } default: fmt.Println("No handler exists for system_message qualifier:", messageQualifier) } case "set_username": if username, ok := msg.Data.(string); ok { //log.Printf("Username request for client %s: %s", c.id, username) if IsUsernameTaken(c.Hub, username) { errorMsg := Message{ Type: "username_error", Error: "Username is already taken. Please choose a different username.", } data, _ := json.Marshal(errorMsg) c.Send <- data continue } if !IsUsernameValid(username) { errorMsg := Message{ Type: "username_error", Error: "Invalid username. Please use 3-24 characters, letters, numbers, underscores, or hyphens.", } data, _ := json.Marshal(errorMsg) c.Send <- data continue } c.Username = username SendUsersList(c.Hub) } else { //log.Printf("Invalid username data type: %T", msg.Data) } case "webrtc_offer": // Forward WebRTC offer to target client data, _ := json.Marshal(Message{ Type: "webrtc_offer", UserID: c.Id, Username: c.Username, Offer: msg.Offer, }) SendToClient(c.Hub, msg.Target, data) case "webrtc_answer": // Forward WebRTC answer to target client data, _ := json.Marshal(Message{ Type: "webrtc_answer", UserID: c.Id, Answer: msg.Answer, }) SendToClient(c.Hub, msg.Target, data) case "webrtc_ice": // Forward ICE candidate to target client data, _ := json.Marshal(Message{ Type: "webrtc_ice", UserID: c.Id, ICE: msg.ICE, }) SendToClient(c.Hub, msg.Target, data) case "speaking": // Broadcast speaking status broadcastMsg := Message{ Type: "user_speaking", UserID: c.Id, Username: c.Username, Data: msg.Data, } data, _ := json.Marshal(broadcastMsg) c.Hub.Broadcast <- data case "chat_message": // Broadcast chat message to all users if msg.Username != "" { // Extract message content from Data field var chatMsg string if msgData, ok := msg.Data.(map[string]any); ok { if message, exists := msgData["message"]; exists { chatMsg, _ = message.(string) } } // Use timestamp from message or current time timestamp := msg.Timestamp if timestamp == 0 { timestamp = time.Now().UnixMilli() } if chatMsg != "" { //log.Printf("Chat message from %s (%s): %s", msg.Username, c.id, chatMsg) broadcastMsg := Message{ Type: "chat_message", Username: msg.Username, Data: map[string]any{ "message": chatMsg, "timestamp": timestamp, }, } data, _ := json.Marshal(broadcastMsg) c.Hub.Broadcast <- data } else { //log.Printf("Empty chat message from client %s", c.id) } } else { //log.Printf("Invalid chat message format from client %s", c.id) } } } } // WritePump writes queued messages to the websocket connection and sends periodic pings. func WritePump(c *server.Client) { defer func(conn *websocket.Conn) { _ = conn.Close() }(c.Conn) for message := range c.Send { err := c.Conn.WriteMessage(websocket.TextMessage, message) if err != nil { return } } }