211 lines
5.0 KiB
Go
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
|
|
}
|
|
}
|
|
|
|
}
|