//! Server-side host for atlibs wasm modules: the Rust twin of javascript/src/server and //! javascript/src/run-module. Same modules, same step protocol, same host //! calls, same state dir. pub mod compose; pub mod crypto; pub mod host; pub mod oauth; pub mod oauth_client; pub mod params; pub mod registry; pub mod run; pub mod serviceauth; pub mod signin; pub mod store; pub mod transport; pub mod wasm; pub mod ws; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use serde_json::Value; use tokio::sync::watch; /// The process's rustls provider (ring), chosen once: atproto-crates' reqwest brings aws-lc-rs into the graph /// beside ring, and then rustls cannot pick one for the clients that ask the process default (wss subscribes). pub fn tls() { let _ = rustls::crypto::ring::default_provider().install_default(); } pub type BoxFut<'a, T> = Pin + Send + 'a>>; /// An AbortSignal: aborted once, with a reason (the JS Error's message). #[derive(Clone)] pub struct Signal(Arc>>); impl Default for Signal { fn default() -> Self { Self(Arc::new(watch::channel(None).0)) } } impl Signal { pub fn new() -> Self { Self::default() } pub fn abort(&self, reason: impl Into) { let reason = reason.into(); self.0 .send_if_modified(|value| value.is_none() && value.replace(reason).is_none()); } pub fn reason(&self) -> Option { self.0.borrow().clone() } pub async fn aborted(&self) -> String { let mut rx = self.0.subscribe(); loop { if let Some(reason) = rx.borrow_and_update().clone() { return reason; } let _ = rx.changed().await; } } /// AbortSignal.any([a, b]): aborts with whichever reason comes first. pub fn any(a: &Signal, b: &Signal) -> Signal { let out = Signal::new(); if let Some(reason) = a.reason().or_else(|| b.reason()) { out.abort(reason); return out; } let (a, b, o) = (a.clone(), b.clone(), out.clone()); tokio::spawn(async move { let reason = tokio::select! { r = a.aborted() => r, r = b.aborted() => r }; o.abort(reason); }); out } } /// `new Date(ms).toISOString()`. pub fn iso(ms: u64) -> String { let (secs, milli) = (ms / 1000, ms % 1000); let (days, sod) = ((secs / 86400) as i64, secs % 86400); let z = days + 719_468; let era = z.div_euclid(146_097); let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let y = yoe + era * 400 + i64::from(m <= 2); format!( "{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}.{milli:03}Z", sod / 3600, sod % 3600 / 60, sod % 60 ) } pub fn now_ms() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map_or(0, |d| d.as_millis() as u64) } pub fn iso_now() -> String { iso(now_ms()) } /// A JS number as `String(n)` prints it. // ponytail: integers and plain decimals; JS switches to exponent form below 1e-6 and from 1e21. pub fn js_num(n: f64) -> String { if n.fract() == 0.0 && n.abs() < 1e21 { format!("{}", n as i128) } else { format!("{n}") } } /// `String(value)` for a JSON value; `None` is undefined. pub fn js_string(value: Option<&Value>) -> String { match value { None => "undefined".into(), Some(Value::Null) => "null".into(), Some(Value::Bool(b)) => b.to_string(), Some(Value::Number(n)) => n .as_i64() .map(|i| i.to_string()) .or_else(|| n.as_u64().map(|u| u.to_string())) .unwrap_or_else(|| js_num(n.as_f64().unwrap_or(f64::NAN))), Some(Value::String(s)) => s.clone(), Some(Value::Array(items)) => items .iter() .map(|v| { if v.is_null() { String::new() } else { js_string(Some(v)) } }) .collect::>() .join(","), Some(Value::Object(_)) => "[object Object]".into(), } } /// `JSON.stringify(value, null, "\t")`. pub fn pretty(value: &Value) -> String { use serde::Serialize; let mut out = Vec::new(); let mut ser = serde_json::Serializer::with_formatter( &mut out, serde_json::ser::PrettyFormatter::with_indent(b"\t"), ); value.serialize(&mut ser).expect("JSON values serialize"); String::from_utf8(out).expect("JSON is UTF-8") } #[cfg(test)] mod tests { use super::*; #[test] fn iso_matches_to_iso_string() { assert_eq!(iso(0), "1970-01-01T00:00:00.000Z"); assert_eq!(iso(1_794_573_296_789), "2026-11-13T12:34:56.789Z"); assert_eq!(iso(951_782_400_000), "2000-02-29T00:00:00.000Z"); } #[test] fn js_string_matches_string() { let v: Value = serde_json::json!([1, null, "a", [2, 3], {"x": 1}, 1.5, true]); assert_eq!(js_string(Some(&v)), "1,,a,2,3,[object Object],1.5,true"); assert_eq!(js_string(None), "undefined"); assert_eq!(js_num(2.0), "2"); } }