use axum::body::Body;
use axum::http::{HeaderValue, Method, StatusCode, Uri, header};
use axum::response::{IntoResponse, Response};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "../../bridge/dist/bridge/browser"]
struct Assets;
pub async fn static_handler(method: Method, uri: Uri) -> Response {
if method != Method::GET && method != Method::HEAD {
return StatusCode::METHOD_NOT_ALLOWED.into_response();
}
let path = uri.path();
if path == "/v1" || path.starts_with("/v1/") {
return StatusCode::NOT_FOUND.into_response();
}
if Assets::get("index.html").is_none() {
return (
StatusCode::OK,
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
"salvor was built without the dashboard — run salvor build to embed it\n",
)
.into_response();
}
let trimmed = path.trim_start_matches('/');
let asset_path = if trimmed.is_empty() {
"index.html"
} else {
trimmed
};
if let Some(file) = Assets::get(asset_path) {
return serve_asset(asset_path, file.data.into_owned());
}
if has_extension(asset_path) {
return StatusCode::NOT_FOUND.into_response();
}
let index = Assets::get("index.html").expect("presence checked above");
serve_asset("index.html", index.data.into_owned())
}
fn serve_asset(name: &str, bytes: Vec<u8>) -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, content_type(name))
.header(header::CACHE_CONTROL, cache_control(name))
.body(Body::from(bytes))
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
fn content_type(name: &str) -> HeaderValue {
let value = match extension(name) {
Some("html") => "text/html; charset=utf-8",
Some("js") | Some("mjs") => "text/javascript; charset=utf-8",
Some("css") => "text/css; charset=utf-8",
Some("json") => "application/json; charset=utf-8",
Some("wasm") => "application/wasm",
Some("ico") => "image/x-icon",
Some("svg") => "image/svg+xml",
Some("png") => "image/png",
Some("jpg") | Some("jpeg") => "image/jpeg",
Some("webp") => "image/webp",
Some("woff2") => "font/woff2",
Some("woff") => "font/woff",
Some("ttf") => "font/ttf",
Some("map") => "application/json; charset=utf-8",
Some("txt") => "text/plain; charset=utf-8",
_ => "application/octet-stream",
};
HeaderValue::from_static(value)
}
fn cache_control(name: &str) -> HeaderValue {
if name == "index.html" {
HeaderValue::from_static("no-cache")
} else if is_hashed(name) {
HeaderValue::from_static("public, max-age=31536000, immutable")
} else {
HeaderValue::from_static("public, max-age=3600")
}
}
fn is_hashed(name: &str) -> bool {
let stem = match name.rsplit_once('.') {
Some((stem, _)) => stem,
None => name,
};
let last = stem.rsplit(&['-', '/'][..]).next().unwrap_or(stem);
last.len() >= 8
&& last
.bytes()
.all(|b| b.is_ascii_uppercase() || b.is_ascii_digit())
}
fn extension(name: &str) -> Option<&str> {
name.rsplit('/')
.next()?
.rsplit_once('.')
.map(|(_, ext)| ext)
}
fn has_extension(path: &str) -> bool {
path.rsplit('/')
.next()
.map(|segment| segment.contains('.'))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashed_chunks_are_recognized() {
assert!(is_hashed("main-MSUQQNP3.js"));
assert!(is_hashed("chunk-2UEFNF5G.js"));
assert!(is_hashed("styles-6FLNUR4M.css"));
}
#[test]
fn plain_names_are_not_hashed() {
assert!(!is_hashed("index.html"));
assert!(!is_hashed("favicon.ico"));
assert!(!is_hashed("salvor_replay_wasm_bg.wasm"));
}
#[test]
fn wasm_is_served_as_a_component_type() {
assert_eq!(content_type("x.wasm"), "application/wasm");
}
#[test]
fn extensionless_paths_are_routes() {
assert!(!has_extension("/runs"));
assert!(!has_extension("/inspector/abc-123"));
assert!(has_extension("/main-MSUQQNP3.js"));
}
}