tangled
alpha
login
or
join now
diffdown.com
/
diffdown-app
0
fork
atom
Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
0
fork
atom
overview
issues
10
pulls
pipelines
feat: add client representation and presence broadcasting
John Luther
3 weeks ago
d6fae998
cd3a87ca
+105
-3
2 changed files
expand all
collapse all
unified
split
internal
collaboration
client.go
hub.go
+78
internal/collaboration/client.go
reviewed
···
1
1
package collaboration
2
2
3
3
import (
4
4
+
"encoding/json"
5
5
+
"log"
6
6
+
4
7
"github.com/gorilla/websocket"
5
8
)
6
9
···
13
16
Color string
14
17
roomKey string
15
18
}
19
19
+
20
20
+
type ClientMessage struct {
21
21
+
Type string `json:"type"`
22
22
+
RKey string `json:"rkey,omitempty"`
23
23
+
DID string `json:"did,omitempty"`
24
24
+
Delta json.RawMessage `json:"delta,omitempty"`
25
25
+
Cursor *CursorPos `json:"cursor,omitempty"`
26
26
+
Comment *CommentMsg `json:"comment,omitempty"`
27
27
+
}
28
28
+
29
29
+
type CursorPos struct {
30
30
+
Position int `json:"position"`
31
31
+
SelectionEnd int `json:"selectionEnd"`
32
32
+
}
33
33
+
34
34
+
type CommentMsg struct {
35
35
+
ParagraphID string `json:"paragraphId"`
36
36
+
Text string `json:"text"`
37
37
+
}
38
38
+
39
39
+
type PresenceUser struct {
40
40
+
DID string `json:"did"`
41
41
+
Name string `json:"name"`
42
42
+
Color string `json:"color"`
43
43
+
}
44
44
+
45
45
+
type PresenceMessage struct {
46
46
+
Type string `json:"type"`
47
47
+
Users []PresenceUser `json:"users"`
48
48
+
}
49
49
+
50
50
+
func NewClient(hub *Hub, conn *websocket.Conn, did, name, color, roomKey string) *Client {
51
51
+
return &Client{
52
52
+
hub: hub,
53
53
+
conn: conn,
54
54
+
send: make(chan []byte, 256),
55
55
+
DID: did,
56
56
+
Name: name,
57
57
+
Color: color,
58
58
+
roomKey: roomKey,
59
59
+
}
60
60
+
}
61
61
+
62
62
+
func (c *Client) ReadPump() {
63
63
+
defer func() {
64
64
+
if room := c.hub.GetRoom(c.roomKey); room != nil {
65
65
+
room.unregister <- c
66
66
+
}
67
67
+
c.conn.Close()
68
68
+
}()
69
69
+
for {
70
70
+
_, message, err := c.conn.ReadMessage()
71
71
+
if err != nil {
72
72
+
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
73
73
+
log.Printf("WebSocket error: %v", err)
74
74
+
}
75
75
+
break
76
76
+
}
77
77
+
_ = message
78
78
+
}
79
79
+
}
80
80
+
81
81
+
func (c *Client) WritePump() {
82
82
+
defer c.conn.Close()
83
83
+
for {
84
84
+
message, ok := <-c.send
85
85
+
if !ok {
86
86
+
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
87
87
+
return
88
88
+
}
89
89
+
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
90
90
+
return
91
91
+
}
92
92
+
}
93
93
+
}
+27
-3
internal/collaboration/hub.go
reviewed
···
1
1
package collaboration
2
2
3
3
import (
4
4
+
"encoding/json"
4
5
"sync"
5
6
)
6
7
···
42
43
return room
43
44
}
44
45
46
46
+
func (h *Hub) GetRoom(rkey string) *Room {
47
47
+
h.mu.RLock()
48
48
+
defer h.mu.RUnlock()
49
49
+
return h.rooms[rkey]
50
50
+
}
51
51
+
45
52
func (r *Room) run() {
46
53
for {
47
54
select {
···
49
56
r.mu.Lock()
50
57
r.clients[client] = true
51
58
r.mu.Unlock()
52
52
-
r.broadcastPresence()
53
59
case client := <-r.unregister:
54
60
r.mu.Lock()
55
61
if _, ok := r.clients[client]; ok {
···
57
63
close(client.send)
58
64
}
59
65
r.mu.Unlock()
60
60
-
r.broadcastPresence()
61
66
case message := <-r.broadcast:
62
67
r.mu.RLock()
63
68
for client := range r.clients {
···
77
82
r.broadcast <- message
78
83
}
79
84
85
85
+
func (r *Room) GetPresence() []PresenceUser {
86
86
+
r.mu.RLock()
87
87
+
defer r.mu.RUnlock()
88
88
+
users := make([]PresenceUser, 0, len(r.clients))
89
89
+
for client := range r.clients {
90
90
+
users = append(users, PresenceUser{
91
91
+
DID: client.DID,
92
92
+
Name: client.Name,
93
93
+
Color: client.Color,
94
94
+
})
95
95
+
}
96
96
+
return users
97
97
+
}
98
98
+
80
99
func (r *Room) broadcastPresence() {
81
81
-
// Implementation in Task 2.2
100
100
+
presence := PresenceMessage{
101
101
+
Type: "presence",
102
102
+
Users: r.GetPresence(),
103
103
+
}
104
104
+
data, _ := json.Marshal(presence)
105
105
+
r.Broadcast(data)
82
106
}