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");