#[cfg(feature = "websocket")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use async_trait::async_trait;
use axum::Router;
use std::sync::Arc;
use tideway::{
App, AppContext, ConfigBuilder, Result,
websocket::{Connection, ConnectionManager, Message, WebSocketHandler, ws},
};
tideway::init_tracing();
let manager = Arc::new(ConnectionManager::new());
struct ChatHandler;
#[async_trait]
impl WebSocketHandler for ChatHandler {
async fn on_connect(&self, conn: &mut Connection, ctx: &tideway::AppContext) -> Result<()> {
tracing::info!(conn_id = %conn.id(), "Client connected");
conn.send_text("Welcome to the chat room! Type /join <room> to join a room, or just start chatting.").await?;
conn.join_room("general");
let manager = ctx.websocket_manager()?;
manager.add_to_room(conn.id(), "general");
manager
.broadcast_text_to_room("general", format!("User {} joined the room", conn.id()))
.await?;
Ok(())
}
async fn on_message(
&self,
conn: &mut Connection,
msg: Message,
ctx: &tideway::AppContext,
) -> Result<()> {
match msg {
Message::Text(text) => {
let manager = ctx.websocket_manager()?;
if text.starts_with("/join ") {
let room_name = text.strip_prefix("/join ").unwrap().trim();
if !room_name.is_empty() {
let rooms_to_leave: Vec<String> =
conn.rooms().iter().cloned().collect();
for room in &rooms_to_leave {
manager.remove_from_room(conn.id(), room);
conn.leave_room(room);
}
conn.join_room(room_name);
manager.add_to_room(conn.id(), room_name);
conn.send_text(format!("Joined room: {}", room_name))
.await?;
manager
.broadcast_text_to_room(
room_name,
format!("User {} joined the room", conn.id()),
)
.await?;
}
} else if text.starts_with("/rooms") {
let rooms: Vec<String> = conn.rooms().iter().cloned().collect();
conn.send_json(&serde_json::json!({
"type": "rooms",
"rooms": rooms
}))
.await?;
} else {
let message = format!("{}: {}", conn.id(), text);
for room in conn.rooms().iter() {
manager.broadcast_text_to_room(room, &message).await?;
}
if conn.rooms().is_empty() {
conn.join_room("general");
manager.add_to_room(conn.id(), "general");
manager.broadcast_text_to_room("general", &message).await?;
}
}
}
Message::Ping(data) => {
conn.send(Message::Pong(data)).await?;
}
_ => {}
}
Ok(())
}
async fn on_disconnect(&self, conn: &mut Connection, ctx: &tideway::AppContext) {
tracing::info!(conn_id = %conn.id(), "Client disconnected");
if let Ok(manager) = ctx.websocket_manager() {
for room in conn.rooms().iter() {
let _ = manager
.broadcast_text_to_room(room, format!("User {} left the room", conn.id()))
.await;
}
}
}
}
let ctx = AppContext::builder()
.with_websocket_manager(manager.clone())
.build();
let config = ConfigBuilder::new().with_log_level("info").build()?;
let ws_router = ws("/ws", ChatHandler, manager);
struct WsModule(Router<tideway::AppContext>);
impl tideway::RouteModule for WsModule {
fn routes(&self) -> Router<tideway::AppContext> {
self.0.clone()
}
fn prefix(&self) -> Option<&str> {
None
}
}
let app = App::with_config(config)
.with_context(ctx)
.register_module(WsModule(ws_router));
println!("WebSocket chat example started!");
println!("Connect to ws://localhost:8000/ws to join the chat");
println!("Commands:");
println!(" /join <room> - Join a room");
println!(" /rooms - List your rooms");
println!(" <message> - Send a message to your current rooms");
app.serve().await?;
Ok(())
}
#[cfg(not(feature = "websocket"))]
fn main() {
println!("This example requires the 'websocket' feature to be enabled.");
println!("Run with: cargo run --example websocket_chat --features websocket");
}