package collaboration import ( "encoding/json" "log" "sync" ) type Hub struct { rooms map[string]*Room mu sync.RWMutex } type Room struct { documentRKey string clients map[*Client]bool broadcast chan *broadcastMsg register chan *Client unregister chan *Client mu sync.RWMutex } // broadcastMsg carries a message and an optional sender to exclude. type broadcastMsg struct { data []byte except *Client // nil = send to all } func NewHub() *Hub { return &Hub{ rooms: make(map[string]*Room), } } func (h *Hub) GetOrCreateRoom(rkey string) *Room { h.mu.Lock() defer h.mu.Unlock() if room, exists := h.rooms[rkey]; exists { return room } room := &Room{ documentRKey: rkey, clients: make(map[*Client]bool), broadcast: make(chan *broadcastMsg, 256), register: make(chan *Client), unregister: make(chan *Client), } h.rooms[rkey] = room go room.run() return room } func (h *Hub) GetRoom(rkey string) *Room { h.mu.RLock() defer h.mu.RUnlock() return h.rooms[rkey] } func (r *Room) run() { for { select { case client := <-r.register: r.mu.Lock() r.clients[client] = true r.mu.Unlock() r.broadcastPresence() case client := <-r.unregister: r.mu.Lock() if _, ok := r.clients[client]; ok { delete(r.clients, client) close(client.send) } r.mu.Unlock() r.broadcastPresence() case msg := <-r.broadcast: r.mu.RLock() for client := range r.clients { if client == msg.except { continue } select { case client.send <- msg.data: default: close(client.send) delete(r.clients, client) } } r.mu.RUnlock() } } } // Broadcast sends to all clients in the room. func (r *Room) Broadcast(data []byte) { r.broadcast <- &broadcastMsg{data: data} } // BroadcastExcept sends to all clients except the given sender. func (r *Room) BroadcastExcept(data []byte, except *Client) { r.broadcast <- &broadcastMsg{data: data, except: except} } func (r *Room) RegisterClient(client *Client) { r.register <- client } func (r *Room) UnregisterClient(client *Client) { r.unregister <- client } func (r *Room) GetPresence() []PresenceUser { r.mu.RLock() defer r.mu.RUnlock() users := make([]PresenceUser, 0, len(r.clients)) for client := range r.clients { users = append(users, PresenceUser{ DID: client.DID, Name: client.Name, Handle: client.Handle, Color: client.Color, Avatar: client.Avatar, }) } return users } func (r *Room) broadcastPresence() { presence := PresenceMessage{ Type: "presence", Users: r.GetPresence(), } data, err := json.Marshal(presence) if err != nil { log.Printf("broadcastPresence: marshal failed: %v", err) return } r.Broadcast(data) }