cargo-leptos 0.0.8

Build tool for Leptos.
use crate::ext::anyhow::{bail, Result};
use crate::logger::{BOLD, GRAY};
use crate::sync::{oneshot_when, shutdown_msg, wait_for_localhost};
use crate::{Config, Msg, MSG_BUS};
use axum::{
    extract::ws::{Message, WebSocket, WebSocketUpgrade},
    response::IntoResponse,
    routing::get,
    Router,
};
use std::net::SocketAddr;
use tokio::{net::TcpStream, task::JoinHandle};

pub async fn spawn(config: &Config) -> Result<JoinHandle<()>> {
    let port = config.leptos.csr_port;
    let route = Router::new().route("/autoreload", get(move |ws| websocket_handler(ws, port)));

    let addr = SocketAddr::from(([127, 0, 0, 1], config.leptos.reload_port));

    if let Ok(_) = TcpStream::connect(&addr).await {
        bail!(
            "Server port {} already in use. You can set which port to use with {} in {} section {}",
            config.leptos.reload_port,
            BOLD.paint("reload_port"),
            BOLD.paint("Cargo.toml"),
            BOLD.paint("[package.metadata.leptos]"),
        );
    }

    let shutdown_rx = oneshot_when(shutdown_msg, "Autoreload");

    log::debug!("Autoreload server started {}", GRAY.paint(addr.to_string()));

    Ok(tokio::spawn(async move {
        match axum::Server::bind(&addr)
            .serve(route.into_make_service())
            .with_graceful_shutdown(async move { drop(shutdown_rx.await.ok()) })
            .await
        {
            Ok(_) => log::debug!("Autoreload server stopped"),
            Err(e) => log::error!("Autoreload {e}"),
        }
    }))
}

async fn websocket_handler(ws: WebSocketUpgrade, port: u16) -> impl IntoResponse {
    ws.on_upgrade(move |stream| websocket(stream, port))
}

async fn websocket(stream: WebSocket, port: u16) {
    let mut rx = MSG_BUS.subscribe();

    log::trace!("Autoreload websocket connected");
    tokio::spawn(async move {
        loop {
            match rx.recv().await {
                Ok(Msg::Reload(msg)) => {
                    if wait_for_localhost(port).await {
                        send_and_close(stream, &msg).await;
                    } else {
                        log::warn!(r#"Autoreload could not send "reload" to websocket"#);
                    }
                    break;
                }
                Err(e) => {
                    log::debug!("Autoreload recive error {e}");
                    break;
                }
                Ok(Msg::ShutDown) => break,
                _ => {}
            }
        }
        log::trace!("Autoreload websocket closed")
    });
}

async fn send_and_close(mut stream: WebSocket, msg: &str) {
    match stream.send(Message::Text(msg.to_string())).await {
        Err(e) => {
            log::debug!("Autoreload could not send {msg} due to {e}");
        }
        Ok(_) => {
            log::debug!("Autoreload sent \"{msg}\" to browser");
        }
    }
    if let Err(e) = stream.close().await {
        log::error!("Autoreload socket close error {e}");
    }
}