Files
libredesk/internal/conversation/ws.go
T
Abhinav Raut 13db8d2843 Fix command palette macros not working due to previous variable rename causing issues.
Improve sidebar navigation to retain open conversation

Fix notification when disabled app still tries sending emails.
2026-03-30 18:51:23 +05:30

168 lines
6.0 KiB
Go

package conversation
import (
"encoding/json"
"time"
cmodels "github.com/abhinavxd/libredesk/internal/conversation/models"
"github.com/abhinavxd/libredesk/internal/inbox"
"github.com/abhinavxd/libredesk/internal/inbox/channel/livechat"
wsmodels "github.com/abhinavxd/libredesk/internal/ws/models"
)
// BroadcastNewMessage broadcasts a new message to all users.
// lastMessage is the computed preview text (e.g., "Image" for media-only messages).
func (m *Manager) BroadcastNewMessage(message *cmodels.Message, lastMessage string) {
data := map[string]any{
"conversation_uuid": message.ConversationUUID,
"content": "",
"created_at": message.CreatedAt.Format(time.RFC3339),
"uuid": message.UUID,
"private": message.Private,
"type": message.Type,
"sender_type": message.SenderType,
}
// Include echo_id from meta so clients can match WS events to pending messages.
var meta map[string]any
if len(message.Meta) > 0 {
if err := json.Unmarshal(message.Meta, &meta); err == nil {
if echoID, ok := meta["echo_id"].(string); ok && echoID != "" {
data["echo_id"] = echoID
}
}
}
m.broadcastToUsers([]int{}, wsmodels.Message{
Type: wsmodels.MessageTypeNewMessage,
Data: data,
})
}
// BroadcastMessageUpdate broadcasts a partial message update to all users.
func (m *Manager) BroadcastMessageUpdate(conversationUUID, messageUUID string, data map[string]any) {
data["conversation_uuid"] = conversationUUID
data["uuid"] = messageUUID
m.broadcastToUsers([]int{}, wsmodels.Message{
Type: wsmodels.MessageTypeMessageUpdate,
Data: data,
})
}
// BroadcastConversationUpdate broadcasts a partial conversation update to all agent clients.
func (m *Manager) BroadcastConversationUpdate(conversationUUID string, data map[string]any) {
data["uuid"] = conversationUUID
m.broadcastToUsers([]int{}, wsmodels.Message{
Type: wsmodels.MessageTypeConversationUpdate,
Data: data,
})
}
// BroadcastContactUpdate broadcasts a contact update to all agent clients.
func (m *Manager) BroadcastContactUpdate(contactID int, data map[string]any) {
data["contact_id"] = contactID
m.broadcastToUsers([]int{}, wsmodels.Message{
Type: "contact_update",
Data: data,
})
}
// BroadcastTypingToConversation broadcasts typing status to all subscribers of a conversation.
// Set broadcastToWidgets to false when the typing event originates from a widget client to avoid echo.
func (m *Manager) BroadcastTypingToConversation(conversationUUID string, isTyping bool, broadcastToWidgets bool) {
message := wsmodels.Message{
Type: wsmodels.MessageTypeTyping,
Data: map[string]any{
"conversation_uuid": conversationUUID,
"is_typing": isTyping,
},
}
messageBytes, err := json.Marshal(message)
if err != nil {
m.lo.Error("error marshalling typing WS message", "error", err)
return
}
// Always broadcast to agent clients (main app WebSocket clients)
m.wsHub.BroadcastTypingToAllConversationClients(conversationUUID, messageBytes)
// Broadcast to widget clients (customers) only if this typing event comes from agents
if broadcastToWidgets {
m.broadcastTypingToWidgetClients(conversationUUID, isTyping)
}
}
// BroadcastTypingToWidgetClientsOnly broadcasts typing status only to widget clients.
func (m *Manager) BroadcastTypingToWidgetClientsOnly(conversationUUID string, isTyping bool) {
m.broadcastTypingToWidgetClients(conversationUUID, isTyping)
}
// broadcastToUsers broadcasts a message to a list of users, if the list is empty it broadcasts to all users.
func (m *Manager) broadcastToUsers(userIDs []int, message wsmodels.Message) {
messageBytes, err := json.Marshal(message)
if err != nil {
m.lo.Error("error marshalling WS message", "error", err)
return
}
m.wsHub.BroadcastMessage(wsmodels.BroadcastMessage{
Data: messageBytes,
Users: userIDs,
})
}
// broadcastTypingToWidgetClients broadcasts typing status to widget clients (customers) for a conversation.
func (m *Manager) broadcastTypingToWidgetClients(conversationUUID string, isTyping bool) {
conversation, err := m.GetConversation(0, conversationUUID, "")
if err != nil {
m.lo.Error("error getting conversation for widget typing broadcast", "error", err, "conversation_uuid", conversationUUID)
return
}
inboxInstance, err := m.inboxStore.Get(conversation.InboxID)
if err != nil {
m.lo.Error("error getting inbox for widget typing broadcast", "error", err, "inbox_id", conversation.InboxID)
return
}
if liveChatInbox, ok := inboxInstance.(*livechat.LiveChat); ok {
liveChatInbox.BroadcastTypingToClients(conversationUUID, conversation.ContactID, isTyping)
}
}
// BroadcastAgentStatusToWidget sends a lightweight assignee availability update
// to widget clients for all active livechat conversations assigned to the given agent.
func (m *Manager) BroadcastAgentStatusToWidget(agentID int, status string) {
var conversations []struct {
UUID string `db:"uuid"`
ContactID int `db:"contact_id"`
InboxID int `db:"inbox_id"`
}
if err := m.q.GetActiveLivechatConversationsByAgent.Select(&conversations, agentID); err != nil {
m.lo.Error("error fetching active livechat conversations for agent", "error", err, "agent_id", agentID)
return
}
for _, conv := range conversations {
m.BroadcastConversationToWidget(conv.UUID, conv.ContactID, conv.InboxID, map[string]any{
"assignee": map[string]any{"availability_status": status},
})
}
}
// BroadcastConversationToWidget broadcasts a partial conversation update to widget clients.
func (m *Manager) BroadcastConversationToWidget(conversationUUID string, contactID, inboxID int, data map[string]any) {
inboxInstance, err := m.inboxStore.Get(inboxID)
if err != nil {
if err == inbox.ErrInboxNotFound {
return
}
m.lo.Error("error getting inbox for widget conversation broadcast", "error", err, "inbox_id", inboxID)
return
}
if liveChatInbox, ok := inboxInstance.(*livechat.LiveChat); ok {
data["uuid"] = conversationUUID
liveChatInbox.BroadcastConversationToClients(conversationUUID, contactID, data)
}
}