1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! 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<Box<dyn Future<Output = T> + Send + 'a>>;
/// An AbortSignal: aborted once, with a reason (the JS Error's message).
#[derive(Clone)]
pub struct Signal(Arc<watch::Sender<Option<String>>>);
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<String>) {
let reason = reason.into();
self.0
.send_if_modified(|value| value.is_none() && value.replace(reason).is_none());
}
pub fn reason(&self) -> Option<String> {
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::<Vec<_>>()
.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");
}
}