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,
forward_console: &serde_json::Value,
) -> 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()))
.replace("__FORWARD_CONSOLE__", &forward_console.to_string())
}
pub(crate) fn resolve_forward_console(
config: Option<&serde_json::Value>,
knobs: &oj_env::Knobs,
) -> serde_json::Value {
let setting = config
.cloned()
.or(knobs.forward_console.map(serde_json::Value::Bool))
.unwrap_or(serde_json::Value::Bool(knobs.agent_env || knobs.hmr_gate));
match setting {
serde_json::Value::Bool(true) => serde_json::json!({
"enabled": true, "unhandledErrors": true, "logLevels": ["error", "warn"],
}),
v if v.is_object() => {
let unhandled = v
.get("unhandledErrors")
.and_then(|b| b.as_bool())
.unwrap_or(true);
let levels = v
.get("logLevels")
.filter(|l| l.is_array())
.cloned()
.unwrap_or_else(|| serde_json::json!([]));
let named = levels.as_array().map(Vec::len).unwrap_or(0);
serde_json::json!({
"enabled": unhandled || named > 0,
"unhandledErrors": unhandled,
"logLevels": levels,
})
}
_ => serde_json::json!({
"enabled": false, "unhandledErrors": false, "logLevels": [],
}),
}
}
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,
}
}