2026-09-27 19:42:49 +03:00
|
|
|
// Package realtime implements the WebSocket hub: real-time delivery of
|
|
|
|
|
// conversation events (messages, typing, presence, read receipts).
|
|
|
|
|
package realtime
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Event type constants sent over the wire.
|
|
|
|
|
const (
|
2026-09-27 21:59:48 +03:00
|
|
|
EventMessageCreated = "message.created"
|
|
|
|
|
EventTyping = "typing"
|
|
|
|
|
EventPresence = "presence"
|
|
|
|
|
EventRead = "read"
|
|
|
|
|
EventConversationAdded = "conversation.added"
|
|
|
|
|
EventConversationRemoved = "conversation.removed"
|
|
|
|
|
EventMemberAdded = "member.added"
|
|
|
|
|
EventMemberRemoved = "member.removed"
|
|
|
|
|
EventReactionAdded = "reaction.added"
|
|
|
|
|
EventReactionRemoved = "reaction.removed"
|
2026-09-27 19:42:49 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Presence status values.
|
|
|
|
|
const (
|
|
|
|
|
StatusOnline = "online"
|
|
|
|
|
StatusOffline = "offline"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MessageCreated is published when a message is stored.
|
|
|
|
|
type MessageCreated struct {
|
2026-09-27 21:59:48 +03:00
|
|
|
TempID string `json:"temp_id,omitempty"`
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
MessageID int64 `json:"message_id"`
|
|
|
|
|
SenderID int64 `json:"sender_id"`
|
|
|
|
|
Body string `json:"body"`
|
|
|
|
|
MediaID *int64 `json:"media_id,omitempty"`
|
|
|
|
|
Media *MediaInfo `json:"media,omitempty"`
|
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MediaInfo mirrors attachment metadata delivered with a message event.
|
|
|
|
|
type MediaInfo struct {
|
|
|
|
|
ID int64 `json:"id"`
|
|
|
|
|
Filename string `json:"filename"`
|
|
|
|
|
ContentType string `json:"content_type"`
|
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
|
URL string `json:"url"`
|
|
|
|
|
ThumbURL string `json:"thumb_url,omitempty"`
|
2026-09-27 19:42:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Typing is published when a user starts or stops typing in a conversation.
|
|
|
|
|
type Typing struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
IsTyping bool `json:"is_typing"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Presence is published when a user comes online or goes offline.
|
|
|
|
|
type Presence struct {
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read is published when a user marks messages in a conversation as read.
|
|
|
|
|
type Read struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
MessageID int64 `json:"message_id"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-27 21:59:48 +03:00
|
|
|
// ConversationChanged is sent to a user when they gain or lose a conversation.
|
|
|
|
|
type ConversationChanged struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MemberChanged is published when a user joins or leaves a conversation.
|
|
|
|
|
type MemberChanged struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReactionChanged is published when a reaction is added to or removed from a message.
|
|
|
|
|
type ReactionChanged struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
MessageID int64 `json:"message_id"`
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
Emoji string `json:"emoji"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-27 19:42:49 +03:00
|
|
|
// envelope is the wire format for every message, both inbound and outbound.
|
|
|
|
|
type envelope struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
Data json.RawMessage `json:"data"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clientTyping is sent by the client to signal typing state.
|
|
|
|
|
type clientTyping struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
IsTyping bool `json:"is_typing"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clientRead is sent by the client to mark messages as read.
|
|
|
|
|
type clientRead struct {
|
|
|
|
|
ConversationID int64 `json:"conversation_id"`
|
|
|
|
|
MessageID int64 `json:"message_id"`
|
|
|
|
|
}
|