WebSocket
Rooms
Group WebSocket clients into named rooms and broadcast to specific groups.
Rooms let you group clients by a logical name and broadcast to a specific group. Common use cases: chat channels, game lobbies, collaboration sessions, topic subscriptions.
Joining and leaving rooms
Manage room membership from within the gateway using the connection manager:
use toni::{injectable, async_trait};
use toni::websocket::{GatewayTrait, WsClient, WsMessage, DisconnectReason, ConnectionManager};
#[injectable(pub struct RoomGateway {
#[inject]
connection_manager: ConnectionManager,
})]
impl RoomGateway {
pub fn new(connection_manager: ConnectionManager) -> Self {
Self { connection_manager }
}
}
#[async_trait]
impl GatewayTrait for RoomGateway {
fn path(&self) -> &str { "/rooms" }
async fn on_connect(&self, client: &WsClient) {
client.send(WsMessage::text("Connected. Send 'join:<room>' to join a room.")).await;
}
async fn on_message(&self, client: &WsClient, message: WsMessage) {
let text = match message.as_text() {
Some(t) => t,
None => return,
};
if let Some(room) = text.strip_prefix("join:") {
self.connection_manager.join_room(client.id(), room).await;
client.send(WsMessage::text(format!("Joined room: {room}"))).await;
// Notify others in the room
self.connection_manager
.broadcast_to_room(room, WsMessage::text(
format!("{} joined", client.id())
))
.await;
} else if let Some(room) = text.strip_prefix("leave:") {
self.connection_manager.leave_room(client.id(), room).await;
client.send(WsMessage::text(format!("Left room: {room}"))).await;
} else if let Some(rest) = text.strip_prefix("msg:") {
// rest format: "<room>:<message>"
if let Some((room, msg)) = rest.split_once(':') {
self.connection_manager
.broadcast_to_room(room, WsMessage::text(
format!("[{room}] {}: {msg}", client.id())
))
.await;
}
}
}
async fn on_disconnect(&self, client: &WsClient, _reason: DisconnectReason) {
// Automatically removed from all rooms on disconnect
self.connection_manager.remove_client(client.id()).await;
}
}ConnectionManager API
manager.join_room(client_id, room_name) // add client to room
manager.leave_room(client_id, room_name) // remove from specific room
manager.remove_client(client_id) // remove from all rooms + disconnect
manager.broadcast_to_room(room, WsMessage) // send to all in room
manager.get_room_members(room_name) // list client IDs in room
manager.get_client_rooms(client_id) // list rooms a client is in
manager.room_count() // total number of active roomsBroadcasting to rooms from services
#[injectable(pub struct ChatService {
#[inject]
connection_manager: ConnectionManager,
})]
impl ChatService {
pub fn new(connection_manager: ConnectionManager) -> Self { Self { connection_manager } }
pub async fn send_to_channel(&self, channel: &str, sender: &str, message: &str) {
let formatted = format!("[{channel}] {sender}: {message}");
self.connection_manager
.broadcast_to_room(channel, WsMessage::text(formatted))
.await;
}
}Module setup
use toni::websocket::BroadcastModule;
#[module(
imports: [BroadcastModule],
providers: [RoomGateway, ChatService],
)]
pub struct ChatModule;