radchat/client.go
2025-09-04 19:42:02 -05:00

211 lines
5.0 KiB
Go

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
}
}
}