use super::*;
pub(crate) fn js(body: impl IntoResponse) -> Response {
([(header::CONTENT_TYPE, "text/javascript")], body).into_response()
}
pub(crate) async fn serve_client_js(
State(state): State<Arc<ServerState>>,
headers: HeaderMap,
) -> Response {
cached_js_response(
&headers,
state.client_js_etag.clone(),
state.client_js.clone(),
)
}
pub(crate) fn cached_js_response(headers: &HeaderMap, etag: String, body: Bytes) -> Response {
if headers
.get(header::IF_NONE_MATCH)
.and_then(|v| v.to_str().ok())
== Some(etag.as_str())
{
return (
StatusCode::NOT_MODIFIED,
[
(header::ETAG, etag),
(header::CACHE_CONTROL, "no-cache".to_string()),
],
)
.into_response();
}
(
[
(header::CONTENT_TYPE, "text/javascript".to_string()),
(header::CACHE_CONTROL, "no-cache".to_string()),
(header::ETAG, etag),
],
body,
)
.into_response()
}
pub(crate) fn hmr_socket_path(hmr: Option<&oj_config::HmrOptions>) -> String {
match hmr
.and_then(|h| h.path.as_deref())
.map(str::trim)
.filter(|p| !p.is_empty())
{
Some(p) if p.starts_with('/') => p.to_string(),
Some(p) => format!("/{p}"),
None => "/__ws".to_string(),
}
}
pub(crate) fn render_client_js(
template: &str,
hmr: Option<&oj_config::HmrOptions>,
ws_path: &str,
token: &str,
) -> String {
let lit = |v: serde_json::Value| v.to_string();
let protocol = hmr.and_then(|h| h.protocol.clone());
let hostname = hmr.and_then(|h| h.host.clone());
let port = hmr.and_then(|h| h.client_port);
let overlay = hmr.and_then(|h| h.overlay).unwrap_or(true);
template
.replace("__HMR_PROTOCOL__", &lit(protocol.into()))
.replace("__HMR_HOSTNAME__", &lit(hostname.into()))
.replace("__HMR_PORT__", &lit(port.into()))
.replace("__HMR_PATH__", &lit(ws_path.into()))
.replace("__HMR_ENABLE_OVERLAY__", &lit(overlay.into()))
.replace("__WS_TOKEN__", &lit(token.into()))
}
pub(crate) fn new_ws_token() -> String {
let mut bytes = [0u8; 16];
let filled = rustls::crypto::aws_lc_rs::default_provider()
.secure_random
.fill(&mut bytes)
.is_ok();
if !filled {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let seed = format!("{}:{nanos}:{:p}", std::process::id(), &bytes);
bytes.copy_from_slice(&blake3::hash(seed.as_bytes()).as_bytes()[..16]);
}
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
pub(crate) fn ws_token_rejected(
check: bool,
token: &str,
headers: &HeaderMap,
query: Option<&str>,
) -> bool {
if !check || !headers.contains_key(header::ORIGIN) {
return false;
}
!query.is_some_and(|q| {
q.split('&')
.any(|kv| kv.strip_prefix("token=") == Some(token))
})
}
pub(crate) fn with_inline_map(code: String, map_json: Option<String>) -> String {
match map_json {
Some(map) => format!(
"{code}\n//# sourceMappingURL={}\n",
oj_compiler::map_json_to_data_url(&map)
),
None => code,
}
}