55//! It does not issue seat credentials and must not be mounted behind a public proxy.
66mod access;
77mod error;
8+ mod stream;
9+
10+ pub use stream:: LiveUpdates ;
811
912use axum:: {
1013 extract:: {
@@ -33,14 +36,34 @@ pub const MAX_BODY_BYTES: usize = 64 * 1024;
3336/// Serve using `into_make_service_with_connect_info::<SocketAddr>()`; requests
3437/// without peer information are refused. The address must already be loopback.
3538pub fn local_router ( service : Arc < GameService > , address : SocketAddr ) -> Result < Router , ApiError > {
39+ build_router ( service, address, None )
40+ }
41+
42+ /// Construct local REST and WebSocket routes using the service's commit observer.
43+ ///
44+ /// The host must install this same LiveUpdates instance with GameService::with_observer.
45+ /// Periodic reconciliation also catches commits from other hosts sharing the store.
46+ pub fn local_router_with_updates (
47+ service : Arc < GameService > ,
48+ address : SocketAddr ,
49+ updates : Arc < LiveUpdates > ,
50+ ) -> Result < Router , ApiError > {
51+ build_router ( service, address, Some ( updates) )
52+ }
53+
54+ fn build_router (
55+ service : Arc < GameService > ,
56+ address : SocketAddr ,
57+ updates : Option < Arc < LiveUpdates > > ,
58+ ) -> Result < Router , ApiError > {
3659 if !address. ip ( ) . is_loopback ( ) || address. port ( ) == 0 {
3760 return Err ( ApiError :: new (
3861 "INVALID_CONFIG" ,
3962 "A bound loopback listener is required" ,
4063 "Bind 127.0.0.1 or ::1, then pass listener.local_addr()." ,
4164 ) ) ;
4265 }
43- Ok ( Router :: new ( )
66+ let mut router = Router :: new ( )
4467 . route ( "/healthz" , get ( health) )
4568 . route ( "/v1/games" , get ( games) )
4669 . route ( "/v1/games/{game_id}" , get ( game) )
@@ -50,7 +73,14 @@ pub fn local_router(service: Arc<GameService>, address: SocketAddr) -> Result<Ro
5073 . route ( "/v1/matches/{id}/actions" , post ( make_move) )
5174 . route ( "/v1/matches/{id}/replay" , get ( replay) )
5275 . fallback ( not_found)
53- . method_not_allowed_fallback ( method_not_allowed)
76+ . method_not_allowed_fallback ( method_not_allowed) ;
77+ if let Some ( updates) = updates {
78+ router = router. route (
79+ "/v1/matches/{id}/stream" ,
80+ get ( stream:: upgrade) . layer ( axum:: Extension ( updates) ) ,
81+ ) ;
82+ }
83+ Ok ( router
5484 . layer ( DefaultBodyLimit :: max ( MAX_BODY_BYTES ) )
5585 . layer ( middleware:: from_fn_with_state (
5686 access:: LocalAccess :: new ( address) ,
0 commit comments