Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com

feat: add collaboration hub with room management

+97
+15
internal/collaboration/client.go
··· 1 + package collaboration 2 + 3 + import ( 4 + "github.com/gorilla/websocket" 5 + ) 6 + 7 + type Client struct { 8 + hub *Hub 9 + conn *websocket.Conn 10 + send chan []byte 11 + DID string 12 + Name string 13 + Color string 14 + roomKey string 15 + }
+82
internal/collaboration/hub.go
··· 1 + package collaboration 2 + 3 + import ( 4 + "sync" 5 + ) 6 + 7 + type Hub struct { 8 + rooms map[string]*Room 9 + mu sync.RWMutex 10 + } 11 + 12 + type Room struct { 13 + documentRKey string 14 + clients map[*Client]bool 15 + broadcast chan []byte 16 + register chan *Client 17 + unregister chan *Client 18 + mu sync.RWMutex 19 + } 20 + 21 + func NewHub() *Hub { 22 + return &Hub{ 23 + rooms: make(map[string]*Room), 24 + } 25 + } 26 + 27 + func (h *Hub) GetOrCreateRoom(rkey string) *Room { 28 + h.mu.Lock() 29 + defer h.mu.Unlock() 30 + if room, exists := h.rooms[rkey]; exists { 31 + return room 32 + } 33 + room := &Room{ 34 + documentRKey: rkey, 35 + clients: make(map[*Client]bool), 36 + broadcast: make(chan []byte, 256), 37 + register: make(chan *Client), 38 + unregister: make(chan *Client), 39 + } 40 + h.rooms[rkey] = room 41 + go room.run() 42 + return room 43 + } 44 + 45 + func (r *Room) run() { 46 + for { 47 + select { 48 + case client := <-r.register: 49 + r.mu.Lock() 50 + r.clients[client] = true 51 + r.mu.Unlock() 52 + r.broadcastPresence() 53 + case client := <-r.unregister: 54 + r.mu.Lock() 55 + if _, ok := r.clients[client]; ok { 56 + delete(r.clients, client) 57 + close(client.send) 58 + } 59 + r.mu.Unlock() 60 + r.broadcastPresence() 61 + case message := <-r.broadcast: 62 + r.mu.RLock() 63 + for client := range r.clients { 64 + select { 65 + case client.send <- message: 66 + default: 67 + close(client.send) 68 + delete(r.clients, client) 69 + } 70 + } 71 + r.mu.RUnlock() 72 + } 73 + } 74 + } 75 + 76 + func (r *Room) Broadcast(message []byte) { 77 + r.broadcast <- message 78 + } 79 + 80 + func (r *Room) broadcastPresence() { 81 + // Implementation in Task 2.2 82 + }