Skip to main content

chat_demo/
chat_demo.rs

1use std::{collections::HashMap, net::SocketAddr, sync::Arc};
2
3use async_trait::async_trait;
4use axum::{Json, Router, response::Html, routing::get};
5use realtime::server::{
6    RealtimeError, RealtimeTokenVerifier, SessionAuth, SocketAppState, SocketServerHandle,
7};
8use serde::Serialize;
9
10const DEFAULT_ADDR: &str = "127.0.0.1:4001";
11
12#[derive(Clone)]
13struct DemoUser {
14    user_id: String,
15    label: String,
16    token: String,
17    roles: Vec<String>,
18}
19
20#[derive(Serialize)]
21struct DemoUserView {
22    user_id: String,
23    label: String,
24    token: String,
25    roles: Vec<String>,
26}
27
28#[derive(Clone)]
29struct StaticTokenVerifier {
30    sessions: Arc<HashMap<String, SessionAuth>>,
31}
32
33impl StaticTokenVerifier {
34    fn new(users: &[DemoUser]) -> Self {
35        let sessions = users
36            .iter()
37            .map(|user| {
38                (
39                    user.token.clone(),
40                    SessionAuth {
41                        user_id: user.user_id.clone(),
42                        roles: user.roles.clone(),
43                    },
44                )
45            })
46            .collect();
47        Self {
48            sessions: Arc::new(sessions),
49        }
50    }
51}
52
53#[async_trait]
54impl RealtimeTokenVerifier for StaticTokenVerifier {
55    async fn verify_token(&self, token: &str) -> Result<SessionAuth, RealtimeError> {
56        let token = token.trim();
57        if token.is_empty() {
58            return Err(RealtimeError::unauthorized("Missing token"));
59        }
60        self.sessions
61            .get(token)
62            .cloned()
63            .ok_or_else(|| RealtimeError::unauthorized("Invalid demo token"))
64    }
65}
66
67#[tokio::main]
68async fn main() -> Result<(), Box<dyn std::error::Error>> {
69    let users = demo_users();
70    let verifier = StaticTokenVerifier::new(&users);
71
72    let server_handle = SocketServerHandle::spawn(Default::default());
73
74    server_handle.on_message("room:lobby", |payload| {
75        println!("Got the payload: {}", payload)
76    });
77
78    let socket_app_state = Arc::new(SocketAppState::new(server_handle, verifier));
79
80    let app = Router::new()
81        .route("/", get(index))
82        .route("/demo/users", get(demo_users_handler))
83        .nest("/api/v1", realtime::server::axum::router(socket_app_state));
84
85    let addr = demo_addr();
86    println!("realtime demo listening on http://{addr}");
87    println!("open http://{addr} in your browser");
88
89    let listener = tokio::net::TcpListener::bind(addr).await?;
90    axum::serve(listener, app).await?;
91    Ok(())
92}
93
94fn demo_users() -> Vec<DemoUser> {
95    vec![
96        DemoUser {
97            user_id: "u-alice".to_string(),
98            label: "Alice".to_string(),
99            token: "demo-token-alice".to_string(),
100            roles: vec!["user".to_string()],
101        },
102        DemoUser {
103            user_id: "u-bob".to_string(),
104            label: "Bob".to_string(),
105            token: "demo-token-bob".to_string(),
106            roles: vec!["user".to_string()],
107        },
108        DemoUser {
109            user_id: "u-admin".to_string(),
110            label: "Admin".to_string(),
111            token: "demo-token-admin".to_string(),
112            roles: vec!["admin".to_string(), "user".to_string()],
113        },
114    ]
115}
116
117fn demo_addr() -> SocketAddr {
118    let raw = std::env::var("REALTIME_DEMO_ADDR").unwrap_or_else(|_| DEFAULT_ADDR.to_string());
119    raw.parse()
120        .unwrap_or_else(|_| panic!("invalid REALTIME_DEMO_ADDR: {raw}"))
121}
122
123async fn index() -> Html<&'static str> {
124    Html(INDEX_HTML)
125}
126
127async fn demo_users_handler() -> Json<Vec<DemoUserView>> {
128    let users = demo_users()
129        .iter()
130        .map(|user| DemoUserView {
131            user_id: user.user_id.clone(),
132            label: user.label.clone(),
133            token: user.token.clone(),
134            roles: user.roles.clone(),
135        })
136        .collect();
137    Json(users)
138}
139
140const INDEX_HTML: &str = include_str!("views/chat_demo.html");