arc_web/websocket/
server.rs1use actix::prelude::*;
2use std::collections::{HashMap, HashSet};
3use tracing::info;
4use uuid::Uuid;
5
6#[derive(Message)]
8#[rtype(result = "()")]
9pub struct Connect {
10 pub id: Uuid,
11 pub user_id: Option<String>,
12 pub addr: Recipient<WsMessage>,
13}
14
15#[derive(Message)]
17#[rtype(result = "()")]
18pub struct Disconnect {
19 pub id: Uuid,
20}
21
22#[derive(Message)]
24#[rtype(result = "()")]
25pub struct Subscribe {
26 pub id: Uuid,
27 pub room: String,
28}
29
30#[derive(Message)]
32#[rtype(result = "()")]
33pub struct Unsubscribe {
34 pub id: Uuid,
35 pub room: String,
36}
37
38#[derive(Message)]
40#[rtype(result = "()")]
41pub struct BroadcastToRoom {
42 pub room: String,
43 pub message: String,
44 pub skip_id: Option<Uuid>,
45}
46
47#[derive(Message)]
49#[rtype(result = "()")]
50pub struct BroadcastToUser {
51 pub user_id: String,
52 pub message: String,
53}
54
55#[derive(Message)]
57#[rtype(result = "()")]
58pub struct BroadcastAll {
59 pub message: String,
60 pub skip_id: Option<Uuid>,
61}
62
63#[derive(Message, Clone)]
65#[rtype(result = "()")]
66pub struct WsMessage(pub String);
67
68struct ConnectionInfo {
70 addr: Recipient<WsMessage>,
71 user_id: Option<String>,
72 rooms: HashSet<String>,
73}
74
75pub struct WsServer {
78 connections: HashMap<Uuid, ConnectionInfo>,
80 rooms: HashMap<String, HashSet<Uuid>>,
82 user_connections: HashMap<String, HashSet<Uuid>>,
84}
85
86impl WsServer {
87 pub fn new() -> Self {
88 WsServer {
89 connections: HashMap::new(),
90 rooms: HashMap::new(),
91 user_connections: HashMap::new(),
92 }
93 }
94
95 fn send_message(&self, id: &Uuid, message: &str) {
97 if let Some(conn) = self.connections.get(id) {
98 conn.addr.do_send(WsMessage(message.to_string()));
99 }
100 }
101
102 pub fn connection_count(&self) -> usize {
104 self.connections.len()
105 }
106}
107
108impl Default for WsServer {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl Actor for WsServer {
115 type Context = Context<Self>;
116}
117
118impl Handler<Connect> for WsServer {
119 type Result = ();
120
121 fn handle(&mut self, msg: Connect, _: &mut Context<Self>) {
122 let user_id_for_log = msg.user_id.clone();
123 let user_id_for_track = msg.user_id.clone();
124 self.connections.insert(
126 msg.id,
127 ConnectionInfo {
128 addr: msg.addr,
129 user_id: msg.user_id,
130 rooms: HashSet::new(),
131 },
132 );
133
134 if let Some(user_id) = user_id_for_track {
136 self.user_connections
137 .entry(user_id)
138 .or_default()
139 .insert(msg.id);
140 }
141
142 info!(connection_id = %msg.id, user_id = ?user_id_for_log, "WebSocket connection established");
143 }
144}
145
146impl Handler<Disconnect> for WsServer {
147 type Result = ();
148
149 fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
150 if let Some(conn) = self.connections.remove(&msg.id) {
151 for room in &conn.rooms {
153 if let Some(room_members) = self.rooms.get_mut(room) {
154 room_members.remove(&msg.id);
155 if room_members.is_empty() {
156 self.rooms.remove(room);
157 }
158 }
159 }
160
161 if let Some(user_id) = conn.user_id {
163 if let Some(user_conns) = self.user_connections.get_mut(&user_id) {
164 user_conns.remove(&msg.id);
165 if user_conns.is_empty() {
166 self.user_connections.remove(&user_id);
167 }
168 }
169 }
170
171 info!(connection_id = %msg.id, "WebSocket connection closed");
172 }
173 }
174}
175
176impl Handler<Subscribe> for WsServer {
177 type Result = ();
178
179 fn handle(&mut self, msg: Subscribe, _: &mut Context<Self>) {
180 self.rooms
182 .entry(msg.room.clone())
183 .or_default()
184 .insert(msg.id);
185
186 if let Some(conn) = self.connections.get_mut(&msg.id) {
188 conn.rooms.insert(msg.room.clone());
189 }
190
191 }
193}
194
195impl Handler<Unsubscribe> for WsServer {
196 type Result = ();
197
198 fn handle(&mut self, msg: Unsubscribe, _: &mut Context<Self>) {
199 if let Some(room_members) = self.rooms.get_mut(&msg.room) {
201 room_members.remove(&msg.id);
202 if room_members.is_empty() {
203 self.rooms.remove(&msg.room);
204 }
205 }
206
207 if let Some(conn) = self.connections.get_mut(&msg.id) {
209 conn.rooms.remove(&msg.room);
210 }
211
212 }
214}
215
216impl Handler<BroadcastToRoom> for WsServer {
217 type Result = ();
218
219 fn handle(&mut self, msg: BroadcastToRoom, _: &mut Context<Self>) {
220 if let Some(members) = self.rooms.get(&msg.room) {
221 for id in members {
222 if let Some(skip_id) = msg.skip_id {
224 if *id == skip_id {
225 continue;
226 }
227 }
228 self.send_message(id, &msg.message);
229 }
230 }
231 }
232}
233
234impl Handler<BroadcastToUser> for WsServer {
235 type Result = ();
236
237 fn handle(&mut self, msg: BroadcastToUser, _: &mut Context<Self>) {
238 if let Some(user_conns) = self.user_connections.get(&msg.user_id) {
239 for id in user_conns {
240 self.send_message(id, &msg.message);
241 }
242 }
243 }
244}
245
246impl Handler<BroadcastAll> for WsServer {
247 type Result = ();
248
249 fn handle(&mut self, msg: BroadcastAll, _: &mut Context<Self>) {
250 for id in self.connections.keys() {
251 if let Some(skip_id) = msg.skip_id {
253 if *id == skip_id {
254 continue;
255 }
256 }
257 self.send_message(id, &msg.message);
258 }
259 }
260}