//! Compiled-in static assets: the stylesheet, fonts and icons. use axum::extract::Path; use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; const CSS: &str = include_str!("../../../../templates/style.css"); const FAVICON: &str = include_str!("../../../../assets/icons/oxygenhub.svg"); /// A short hash of the stylesheets and script the pages link, put in their /// URLs (`?v=…`): they are served for a long time and cached by any proxy /// in front of the hub (Cloudflare held a day-old stylesheet), so a new /// version must have a new URL. pub fn assets_version() -> &'static str { static V: std::sync::OnceLock = std::sync::OnceLock::new(); V.get_or_init(|| { let mut h = blake3::Hasher::new(); h.update(CSS.as_bytes()); for a in ASSETS.iter().filter(|a| { matches!( a.path, "github.css" | "hub.js" | "early.js" | "pulse.js" | "backdrop-boot.js" | "report-frame.js" | "report.css" | "backdrop/backdrop.js" | "backdrop/backdrop_bg.wasm" ) }) { h.update(a.bytes); } h.finalize().to_hex()[..12].to_string() }) } struct Asset { path: &'static str, mime: &'static str, bytes: &'static [u8], } const ASSETS: &[Asset] = &[ Asset { path: "fonts/Barlow-300.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-300.woff2") }, Asset { path: "fonts/Barlow-400.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-400.woff2") }, Asset { path: "fonts/Barlow-500.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-500.woff2") }, Asset { path: "fonts/Barlow-600.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/Barlow-600.woff2") }, Asset { path: "fonts/PressStart2P-400.woff2", mime: "font/woff2", bytes: include_bytes!("../../../../assets/fonts/PressStart2P-400.woff2"), }, Asset { path: "github.css", mime: "text/css; charset=utf-8", bytes: include_bytes!("../../../../assets/github.css") }, Asset { path: "hub.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/hub.js") }, // What pages ran inline before the CSP was enforced (#227): the look // and theme before first paint, the pulse chart, the backdrop's start. Asset { path: "early.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/early.js") }, Asset { path: "pulse.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/pulse.js") }, Asset { path: "backdrop-boot.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/backdrop-boot.js"), }, // For report frames (#127): the size and theme bridge the hub adds to // a report's pages, and the stylesheet a report may link. Asset { path: "report-frame.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/report-frame.js") }, Asset { path: "report.css", mime: "text/css; charset=utf-8", bytes: include_bytes!("../../../../assets/report.css") }, // Backdrops (docs/backgrounds.md, #157): the engine, Rust compiled to // WebAssembly (crates/backdrop), and its wasm-bindgen glue, both // written by `make backdrop`. Asset { path: "backdrop/backdrop.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/backdrop/backdrop.js"), }, Asset { path: "backdrop/backdrop_bg.wasm", mime: "application/wasm", bytes: include_bytes!("../../../../assets/backdrop/backdrop_bg.wasm"), }, Asset { path: "vendor/editor.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/vendor/editor.js"), }, Asset { path: "vendor/easymde.min.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/vendor/easymde.min.js"), }, // Mermaid 12.1.0 (MIT, assets/vendor/mermaid.LICENSE), from npm's // mermaid package, dist/mermaid.min.js; loaded only by pages with a // diagram. Asset { path: "vendor/mermaid.min.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/vendor/mermaid.min.js"), }, Asset { path: "wasi-worker.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/wasi-worker.js") }, Asset { path: "wasm-tab.js", mime: "text/javascript; charset=utf-8", bytes: include_bytes!("../../../../assets/wasm-tab.js") }, Asset { path: "vendor/easymde.min.css", mime: "text/css; charset=utf-8", bytes: include_bytes!("../../../../assets/vendor/easymde.min.css"), }, ]; const CACHE: &str = "public, max-age=86400"; pub async fn style() -> Response { ([(header::CONTENT_TYPE, "text/css; charset=utf-8"), (header::CACHE_CONTROL, CACHE)], CSS).into_response() } pub async fn favicon() -> Response { ([(header::CONTENT_TYPE, "image/svg+xml"), (header::CACHE_CONTROL, CACHE)], FAVICON).into_response() } pub async fn asset(Path(path): Path) -> Response { match ASSETS.iter().find(|a| a.path == path) { // Readable across origins: a report's sandboxed frame has an // opaque origin, and loads fonts in CORS mode (#127). Some(a) => ( [ (header::CONTENT_TYPE, a.mime), (header::CACHE_CONTROL, "public, max-age=31536000, immutable"), (header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"), ], a.bytes, ) .into_response(), None => StatusCode::NOT_FOUND.into_response(), } }