web_transport_node/
lib.rs1use napi::bindgen_prelude::*;
2use napi_derive::napi;
3
4use tokio::sync::Mutex;
5
6fn session_error_to_close_info(err: &web_transport_quinn::SessionError) -> NapiCloseInfo {
7 match err {
8 web_transport_quinn::SessionError::WebTransportError(
9 web_transport_quinn::WebTransportError::Closed(code, reason),
10 ) => NapiCloseInfo {
11 close_code: *code,
12 reason: reason.clone(),
13 },
14 other => NapiCloseInfo {
15 close_code: 0,
16 reason: other.to_string(),
17 },
18 }
19}
20
21#[napi]
23pub struct NapiClient {
24 inner: web_transport_quinn::Client,
25}
26
27#[napi]
28impl NapiClient {
29 #[napi(factory)]
31 pub fn with_system_roots() -> Result<Self> {
32 napi::bindgen_prelude::within_runtime_if_available(|| {
33 let client = web_transport_quinn::ClientBuilder::new()
34 .with_system_roots()
35 .map_err(|e| Error::from_reason(e.to_string()))?;
36 Ok(Self { inner: client })
37 })
38 }
39
40 #[napi(factory)]
43 pub fn disable_verify() -> Result<Self> {
44 napi::bindgen_prelude::within_runtime_if_available(|| {
45 let client = web_transport_quinn::ClientBuilder::new()
46 .dangerous()
47 .with_no_certificate_verification()
48 .map_err(|e| Error::from_reason(e.to_string()))?;
49 Ok(Self { inner: client })
50 })
51 }
52
53 #[napi(factory)]
55 pub fn with_certificate_hashes(hashes: Vec<Buffer>) -> Result<Self> {
56 napi::bindgen_prelude::within_runtime_if_available(|| {
57 let hashes: Vec<Vec<u8>> = hashes.into_iter().map(|b| b.to_vec()).collect();
58 let client = web_transport_quinn::ClientBuilder::new()
59 .with_server_certificate_hashes(hashes)
60 .map_err(|e| Error::from_reason(e.to_string()))?;
61 Ok(Self { inner: client })
62 })
63 }
64
65 #[napi]
67 pub async fn connect(&self, url_str: String) -> Result<NapiSession> {
68 let url: url::Url = url_str
69 .parse()
70 .map_err(|e: url::ParseError| Error::from_reason(e.to_string()))?;
71 let session = self
72 .inner
73 .connect(url)
74 .await
75 .map_err(|e| Error::from_reason(e.to_string()))?;
76 Ok(NapiSession {
77 inner: session.clone(),
78 closed: Mutex::new(None),
79 })
80 }
81}
82
83#[napi]
85pub struct NapiServer {
86 inner: Mutex<web_transport_quinn::Server>,
87}
88
89#[napi]
90impl NapiServer {
91 #[napi(factory)]
93 pub fn bind(addr: String, cert_pem: Buffer, key_pem: Buffer) -> Result<Self> {
94 napi::bindgen_prelude::within_runtime_if_available(|| {
95 let certs = rustls_pemfile::certs(&mut &cert_pem[..])
96 .collect::<std::result::Result<Vec<_>, _>>()
97 .map_err(|e| Error::from_reason(format!("invalid certificate PEM: {e}")))?;
98 let key = rustls_pemfile::private_key(&mut &key_pem[..])
99 .map_err(|e| Error::from_reason(format!("invalid private key PEM: {e}")))?
100 .ok_or_else(|| Error::from_reason("no private key found in PEM"))?;
101
102 let addr: std::net::SocketAddr = addr
103 .parse()
104 .map_err(|e: std::net::AddrParseError| Error::from_reason(e.to_string()))?;
105
106 let server = web_transport_quinn::ServerBuilder::new()
107 .with_addr(addr)
108 .with_certificate(certs, key)
109 .map_err(|e| Error::from_reason(e.to_string()))?;
110
111 Ok(Self {
112 inner: Mutex::new(server),
113 })
114 })
115 }
116
117 #[napi]
119 pub async fn accept(&self) -> Result<Option<NapiRequest>> {
120 let mut server = self.inner.lock().await;
121 match server.accept().await {
122 Some(request) => Ok(Some(NapiRequest {
123 inner: Mutex::new(Some(request)),
124 })),
125 None => Ok(None),
126 }
127 }
128}
129
130#[napi]
132pub struct NapiRequest {
133 inner: Mutex<Option<web_transport_quinn::Request>>,
135}
136
137#[napi]
138impl NapiRequest {
139 #[napi(getter)]
141 pub async fn url(&self) -> Result<String> {
142 let guard = self.inner.lock().await;
143 let request = guard
144 .as_ref()
145 .ok_or_else(|| Error::from_reason("request already consumed"))?;
146 Ok(request.url.to_string())
147 }
148
149 #[napi]
151 pub async fn ok(&self) -> Result<NapiSession> {
152 let request = self
153 .inner
154 .lock()
155 .await
156 .take()
157 .ok_or_else(|| Error::from_reason("request already consumed"))?;
158 let session = request
159 .ok()
160 .await
161 .map_err(|e| Error::from_reason(e.to_string()))?;
162 Ok(NapiSession {
163 inner: session.clone(),
164 closed: Mutex::new(None),
165 })
166 }
167
168 #[napi]
170 pub async fn reject(&self, status: u16) -> Result<()> {
171 let request = self
172 .inner
173 .lock()
174 .await
175 .take()
176 .ok_or_else(|| Error::from_reason("request already consumed"))?;
177 let status = http::StatusCode::from_u16(status)
178 .map_err(|e| Error::from_reason(format!("invalid status code: {e}")))?;
179 request
180 .reject(status)
181 .await
182 .map_err(|e| Error::from_reason(e.to_string()))?;
183 Ok(())
184 }
185}
186
187#[napi]
189pub struct NapiSession {
190 inner: web_transport_quinn::Session,
191 closed: Mutex<Option<NapiCloseInfo>>,
193}
194
195#[derive(Clone)]
197#[napi(object)]
198pub struct NapiCloseInfo {
199 pub close_code: u32,
200 pub reason: String,
201}
202
203#[napi]
205pub struct NapiBiStream {
206 send: Option<NapiSendStream>,
207 recv: Option<NapiRecvStream>,
208}
209
210#[napi]
211impl NapiBiStream {
212 #[napi]
214 pub fn take_send(&mut self) -> Result<NapiSendStream> {
215 self.send
216 .take()
217 .ok_or_else(|| Error::from_reason("send stream already taken"))
218 }
219
220 #[napi]
222 pub fn take_recv(&mut self) -> Result<NapiRecvStream> {
223 self.recv
224 .take()
225 .ok_or_else(|| Error::from_reason("recv stream already taken"))
226 }
227}
228
229#[napi]
230impl NapiSession {
231 #[napi]
233 pub async fn accept_uni(&self) -> Result<NapiRecvStream> {
234 let recv = self
235 .inner
236 .accept_uni()
237 .await
238 .map_err(|e| Error::from_reason(e.to_string()))?;
239 Ok(NapiRecvStream {
240 inner: Mutex::new(recv),
241 })
242 }
243
244 #[napi]
246 pub async fn accept_bi(&self) -> Result<NapiBiStream> {
247 let (send, recv) = self
248 .inner
249 .accept_bi()
250 .await
251 .map_err(|e| Error::from_reason(e.to_string()))?;
252 Ok(NapiBiStream {
253 send: Some(NapiSendStream {
254 inner: Mutex::new(send),
255 }),
256 recv: Some(NapiRecvStream {
257 inner: Mutex::new(recv),
258 }),
259 })
260 }
261
262 #[napi]
264 pub async fn open_uni(&self) -> Result<NapiSendStream> {
265 let send = self
266 .inner
267 .open_uni()
268 .await
269 .map_err(|e| Error::from_reason(e.to_string()))?;
270 Ok(NapiSendStream {
271 inner: Mutex::new(send),
272 })
273 }
274
275 #[napi]
277 pub async fn open_bi(&self) -> Result<NapiBiStream> {
278 let (send, recv) = self
279 .inner
280 .open_bi()
281 .await
282 .map_err(|e| Error::from_reason(e.to_string()))?;
283 Ok(NapiBiStream {
284 send: Some(NapiSendStream {
285 inner: Mutex::new(send),
286 }),
287 recv: Some(NapiRecvStream {
288 inner: Mutex::new(recv),
289 }),
290 })
291 }
292
293 #[napi]
295 pub fn send_datagram(&self, data: Buffer) -> Result<()> {
296 within_runtime_if_available(|| {
297 self.inner
298 .send_datagram(bytes::Bytes::from(data.to_vec()))
299 .map_err(|e| Error::from_reason(e.to_string()))
300 })
301 }
302
303 #[napi]
305 pub async fn recv_datagram(&self) -> Result<Buffer> {
306 let data = self
307 .inner
308 .read_datagram()
309 .await
310 .map_err(|e| Error::from_reason(e.to_string()))?;
311 Ok(Buffer::from(data.to_vec()))
312 }
313
314 #[napi]
316 pub fn max_datagram_size(&self) -> u32 {
317 within_runtime_if_available(|| self.inner.max_datagram_size() as u32)
318 }
319
320 #[napi]
322 pub fn close(&self, code: u32, reason: String) {
323 within_runtime_if_available(|| {
324 self.inner.close(code, reason.as_bytes());
325 });
326 }
327
328 #[napi]
330 pub async fn closed(&self) -> Result<NapiCloseInfo> {
331 {
333 let cached = self.closed.lock().await;
334 if let Some(info) = cached.as_ref() {
335 return Ok(info.clone());
336 }
337 }
338
339 let err = self.inner.closed().await;
340 let info = session_error_to_close_info(&err);
341
342 {
344 let mut cached = self.closed.lock().await;
345 *cached = Some(info.clone());
346 }
347
348 Ok(info)
349 }
350}
351
352#[napi]
354pub struct NapiSendStream {
355 inner: Mutex<web_transport_quinn::SendStream>,
356}
357
358#[napi]
359impl NapiSendStream {
360 #[napi]
362 pub async fn write(&self, data: Buffer) -> Result<()> {
363 let mut stream = self.inner.lock().await;
364 stream
365 .write_all(&data)
366 .await
367 .map_err(|e| Error::from_reason(e.to_string()))
368 }
369
370 #[napi]
372 pub async fn finish(&self) -> Result<()> {
373 let mut stream = self.inner.lock().await;
374 stream
375 .finish()
376 .map_err(|e| Error::from_reason(e.to_string()))
377 }
378
379 #[napi]
381 pub async fn reset(&self, code: u32) -> Result<()> {
382 let mut stream = self.inner.lock().await;
383 stream
384 .reset(code)
385 .map_err(|e| Error::from_reason(e.to_string()))
386 }
387
388 #[napi]
390 pub async fn set_priority(&self, priority: i32) -> Result<()> {
391 let stream = self.inner.lock().await;
392 stream
393 .set_priority(priority)
394 .map_err(|e| Error::from_reason(e.to_string()))
395 }
396}
397
398#[napi]
400pub struct NapiRecvStream {
401 inner: Mutex<web_transport_quinn::RecvStream>,
402}
403
404#[napi]
405impl NapiRecvStream {
406 #[napi]
408 pub async fn read(&self, max_size: u32) -> Result<Option<Buffer>> {
409 let mut stream = self.inner.lock().await;
410 let Some(chunk) = stream
411 .read_chunk(max_size as usize, true)
412 .await
413 .map_err(|e| Error::from_reason(e.to_string()))?
414 else {
415 return Ok(None);
416 };
417
418 Ok(Some(Buffer::from(chunk.bytes.to_vec())))
419 }
420
421 #[napi]
423 pub async fn stop(&self, code: u32) -> Result<()> {
424 let mut stream = self.inner.lock().await;
425 stream
426 .stop(code)
427 .map_err(|e| Error::from_reason(e.to_string()))
428 }
429}