mod assets;
pub mod features;
mod live;
mod server;
mod snapshot;
mod token;
pub mod tools;
use crate::ipc::WebEndpoint;
use anyhow::{Context, Result};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::Path;
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
const DEFAULT_LISTEN: &str = "127.0.0.1:7878";
pub async fn start(token_path: &Path) -> Result<(WebEndpoint, JoinHandle<()>)> {
let token = token::load_or_create_at(token_path)?;
let listener = bind_loopback().await?;
start_with_token(listener, token).await
}
pub async fn start_with_listener(listener: TcpListener) -> Result<(WebEndpoint, JoinHandle<()>)> {
start_with_token(listener, token::ephemeral()).await
}
pub async fn start_with_token(
listener: TcpListener,
token: String,
) -> Result<(WebEndpoint, JoinHandle<()>)> {
let addr = listener.local_addr()?;
let endpoint = endpoint(addr, token);
let app = server::router(endpoint.token.clone());
let task = tokio::spawn(async move {
if let Err(err) = axum::serve(listener, app).await {
tracing::warn!(%err, "daemon web app stopped");
}
});
Ok((endpoint, task))
}
async fn bind_loopback() -> Result<TcpListener> {
match TcpListener::bind(DEFAULT_LISTEN).await {
Ok(listener) => Ok(listener),
Err(err) => bind_fallback()
.await
.with_context(|| format!("bind daemon web app at {DEFAULT_LISTEN}: {err}")),
}
}
async fn bind_fallback() -> Result<TcpListener> {
TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.map_err(Into::into)
}
fn endpoint(addr: SocketAddr, token: String) -> WebEndpoint {
let public = public_addr(addr);
WebEndpoint {
listen: addr.to_string(),
url: format!("http://{public}/?token={token}"),
token,
}
}
fn public_addr(addr: SocketAddr) -> SocketAddr {
if addr.ip().is_unspecified() {
return SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), addr.port());
}
addr
}