infinity_bridge_host/
server.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use axum::Router;
5use axum::extract::State;
6use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
7use axum::response::IntoResponse;
8use axum::routing::get;
9use futures::{SinkExt, StreamExt};
10use infinity_bridge_wire::{BridgeError, EventPayload, READY_EVENT, WireMsg};
11use serde_json::Value;
12use tokio::sync::{broadcast, mpsc, oneshot, watch};
13
14use crate::hub::{ClientInfo, Hub};
15
16pub struct ServerConfig {
17 pub bind_addr: String,
18 pub ws_path: String,
19 pub event_capacity: usize,
20 pub ping_interval: Duration,
21 pub ping_timeout: Duration,
22}
23
24impl ServerConfig {
25 pub fn new(bind_addr: impl Into<String>, ws_path: impl Into<String>) -> Self {
26 Self {
27 bind_addr: bind_addr.into(),
28 ws_path: ws_path.into(),
29 event_capacity: 256,
30 ping_interval: Duration::from_secs(5),
31 ping_timeout: Duration::from_secs(15),
32 }
33 }
34}
35
36#[derive(Clone)]
37pub struct BridgeServer {
38 hub: Arc<Hub>,
39 config: Arc<ServerConfig>,
40}
41
42impl BridgeServer {
43 pub async fn start(config: ServerConfig) -> Result<Self, BridgeError> {
49 let hub = Hub::new(config.event_capacity);
50 let config = Arc::new(config);
51
52 let server = Self {
53 hub: Arc::clone(&hub),
54 config: Arc::clone(&config),
55 };
56
57 let app_state = AppState {
58 hub: Arc::clone(&hub),
59 config: Arc::clone(&config),
60 };
61
62 let app = Router::new()
63 .route(&config.ws_path, get(ws_upgrade))
64 .route("/health", get(health))
65 .with_state(app_state);
66
67 let listener = tokio::net::TcpListener::bind(&config.bind_addr)
68 .await
69 .map_err(|e| BridgeError::transport(format!("bind failed: {e}")))?;
70
71 tokio::spawn(async move {
72 if let Err(e) = axum::serve(listener, app).await {
73 eprintln!("[infinity-bridge-host] Server error: {e}");
74 }
75 });
76
77 Ok(server)
78 }
79
80 pub fn router(config: ServerConfig) -> (Self, Router) {
85 let hub = Hub::new(config.event_capacity);
86 let config = Arc::new(config);
87
88 let server = Self {
89 hub: Arc::clone(&hub),
90 config: Arc::clone(&config),
91 };
92
93 let app_state = AppState {
94 hub: Arc::clone(&hub),
95 config: Arc::clone(&config),
96 };
97
98 let router = Router::new()
99 .route(&config.ws_path, get(ws_upgrade))
100 .route("/health", get(health))
101 .with_state(app_state);
102
103 (server, router)
104 }
105
106 pub async fn command(
110 &self,
111 name: &str,
112 payload: Value,
113 timeout: Duration,
114 ) -> Result<Value, BridgeError> {
115 self.hub.command(Some(name), payload, timeout).await
116 }
117
118 pub async fn command_raw(
120 &self,
121 payload: Value,
122 timeout: Duration,
123 ) -> Result<Value, BridgeError> {
124 self.hub.command(None, payload, timeout).await
125 }
126
127 pub async fn emit(&self, name: impl Into<String>, data: Value) -> Result<(), BridgeError> {
134 self.hub.emit(name, data).await
135 }
136
137 pub fn subscribe_events(&self) -> broadcast::Receiver<EventPayload> {
143 self.hub.subscribe_events()
144 }
145
146 pub async fn is_connected(&self) -> bool {
153 self.hub.is_connected().await
154 }
155
156 pub async fn is_ready(&self) -> bool {
162 self.hub.is_ready().await
163 }
164
165 pub async fn wait_connected(&self) {
167 self.hub.wait_connected().await
168 }
169
170 pub fn connection_status(&self) -> watch::Receiver<bool> {
175 self.hub.subscribe_connection_status()
176 }
177
178 pub async fn clients(&self) -> Vec<ClientInfo> {
180 self.hub.connected_clients().await
181 }
182}
183
184#[derive(Clone)]
185struct AppState {
186 hub: Arc<Hub>,
187 config: Arc<ServerConfig>,
188}
189
190async fn health() -> impl IntoResponse {
191 "ok"
192}
193
194async fn ws_upgrade(ws: WebSocketUpgrade, State(state): State<AppState>) -> impl IntoResponse {
195 ws.on_upgrade(move |socket| handle_socket(socket, state))
196}
197
198async fn handle_socket(socket: WebSocket, state: AppState) {
199 let (mut ws_tx, mut ws_rx) = socket.split();
200 let (out_tx, mut out_rx) = mpsc::unbounded_channel::<String>();
201
202 let client_id = state.hub.register_client(out_tx.clone()).await;
203
204 let writer = tokio::spawn(async move {
205 while let Some(text) = out_rx.recv().await {
206 if ws_tx.send(Message::Text(text.into())).await.is_err() {
207 break;
208 }
209 }
210 });
211
212 let hub_for_ping = Arc::clone(&state.hub);
213 let ping_interval = state.config.ping_interval;
214 let ping_timeout = state.config.ping_timeout;
215 let (reaped_tx, mut reaped_rx) = oneshot::channel::<()>();
223 let ping_task = tokio::spawn(async move {
224 let mut interval = tokio::time::interval(ping_interval);
225 interval.tick().await;
226 let mut reaped_tx = Some(reaped_tx);
227 loop {
228 interval.tick().await;
229
230 {
231 let dead = hub_for_ping.reap_dead_clients(ping_timeout).await;
232 if dead.contains(&client_id) {
233 if let Some(tx) = reaped_tx.take() {
234 let _ = tx.send(());
235 }
236 break;
237 }
238 }
239
240 let ping = WireMsg::Ping {
241 ts: Some(
242 std::time::SystemTime::now()
243 .duration_since(std::time::UNIX_EPOCH)
244 .unwrap_or_default()
245 .as_millis() as u64,
246 ),
247 };
248 let json = match ping.to_json() {
249 Ok(j) => j,
250 Err(_) => continue,
251 };
252 if hub_for_ping.send_to(client_id, json).await.is_err() {
253 if let Some(tx) = reaped_tx.take() {
254 let _ = tx.send(());
255 }
256 break;
257 }
258 }
259 });
260
261 loop {
262 let msg = tokio::select! {
263 _ = &mut reaped_rx => break,
266 next = ws_rx.next() => match next {
267 Some(Ok(msg)) => msg,
268 _ => break,
269 },
270 };
271
272 match msg {
273 Message::Text(text) => {
274 state.hub.touch_client(client_id).await;
275
276 let wire = match WireMsg::from_json(&text) {
277 Ok(w) => w,
278 Err(_) => continue,
279 };
280
281 match wire {
282 WireMsg::Hello(hello) => {
283 state.hub.set_client_hello(client_id, hello).await;
284 }
285 WireMsg::Ack(ack) => {
286 state.hub.dispatch_ack(ack).await;
287 }
288 WireMsg::Event(event) if event.name == READY_EVENT => {
291 let ready = event
292 .data
293 .get("ready")
294 .and_then(Value::as_bool)
295 .unwrap_or(true);
296 state.hub.set_client_ready(client_id, ready).await;
297 }
298 WireMsg::Event(event) => {
299 state.hub.dispatch_event(event);
300 }
301 WireMsg::Pong { .. } => {
302 }
304 WireMsg::Cmd(cmd) => {
305 state.hub.dispatch_event(EventPayload::new(
306 cmd.name.unwrap_or_else(|| "cmd".into()),
307 cmd.payload,
308 ));
309 }
310 WireMsg::Ping { ts } => {
311 let pong = WireMsg::Pong { ts };
312 if let Ok(json) = pong.to_json() {
313 let _ = state.hub.send_to(client_id, json).await;
314 }
315 }
316 }
317 }
318 Message::Close(_) => break,
319 _ => {}
320 }
321 }
322
323 state.hub.unregister_client(client_id).await;
324 ping_task.abort();
325 drop(out_tx);
326 let _ = writer.await;
327}