1use std::sync::{Arc, Mutex};
20
21use crate::{BlobDownload, BlobUploadGrant, SegmentRequest, Transport, TransportError};
22
23pub enum Inbound {
25 Text(String),
26 Binary(Vec<u8>),
27}
28
29pub struct InboundBuffer {
33 frames: Mutex<Vec<Inbound>>,
34 notify: Option<Arc<dyn Fn() + Send + Sync>>,
35}
36
37impl Default for InboundBuffer {
38 fn default() -> Self {
39 Self {
40 frames: Mutex::new(Vec::new()),
41 notify: None,
42 }
43 }
44}
45
46impl InboundBuffer {
47 pub fn with_notify(notify: Arc<dyn Fn() + Send + Sync>) -> Self {
48 Self {
49 frames: Mutex::new(Vec::new()),
50 notify: Some(notify),
51 }
52 }
53
54 pub fn push(&self, frame: Inbound) {
55 self.frames.lock().expect("inbound lock").push(frame);
56 if let Some(notify) = &self.notify {
57 notify();
58 }
59 }
60 fn take(&self) -> Vec<Inbound> {
61 std::mem::take(&mut *self.frames.lock().expect("inbound lock"))
62 }
63}
64
65pub enum HostTransport {
66 Null {
68 signed_urls: bool,
69 inbound: Arc<InboundBuffer>,
70 },
71 #[cfg(feature = "native-transport")]
72 Native(native::NativeTransport),
73}
74
75impl HostTransport {
76 pub fn from_config(config: &serde_json::Value) -> Result<Self, String> {
79 Self::new_from_config(config)
80 }
81
82 pub fn new_from_config(config: &serde_json::Value) -> Result<Self, String> {
85 Self::from_config_with_notify(config, None)
86 }
87
88 pub fn from_config_with_notify(
92 config: &serde_json::Value,
93 notify: Option<Arc<dyn Fn() + Send + Sync>>,
94 ) -> Result<Self, String> {
95 #[cfg(feature = "native-transport")]
96 {
97 if let Some(base_url) = config.get("baseUrl").and_then(|v| v.as_str()) {
98 return Ok(HostTransport::Native(native::NativeTransport::new(
99 base_url, config, notify,
100 )?));
101 }
102 }
103 #[cfg(not(feature = "native-transport"))]
104 {
105 if config.get("baseUrl").is_some() {
106 return Err(
107 "this build has no native transport (rebuild with --features native-transport)"
108 .to_owned(),
109 );
110 }
111 }
112 Ok(HostTransport::Null {
113 signed_urls: false,
114 inbound: Arc::new(match notify {
115 Some(notify) => InboundBuffer::with_notify(notify),
116 None => InboundBuffer::default(),
117 }),
118 })
119 }
120
121 pub fn set_signed_urls(&mut self, value: bool) {
122 match self {
123 HostTransport::Null { signed_urls, .. } => *signed_urls = value,
124 #[cfg(feature = "native-transport")]
125 HostTransport::Native(t) => t.signed_urls = value,
126 }
127 }
128
129 pub fn set_headers(&mut self, headers: Vec<(String, String)>) {
132 match self {
133 HostTransport::Null { .. } => drop(headers),
134 #[cfg(feature = "native-transport")]
135 HostTransport::Native(t) => t.set_headers(headers),
136 }
137 }
138
139 pub fn take_inbound(&mut self) -> Vec<Inbound> {
141 match self {
142 HostTransport::Null { inbound, .. } => inbound.take(),
143 #[cfg(feature = "native-transport")]
144 HostTransport::Native(t) => t.inbound.take(),
145 }
146 }
147
148 pub fn shutdown(&mut self) {
150 match self {
151 HostTransport::Null { .. } => {}
152 #[cfg(feature = "native-transport")]
153 HostTransport::Native(t) => t.shutdown(),
154 }
155 }
156}
157
158fn unavailable(op: &str) -> TransportError {
159 TransportError::new(
160 "transport.unavailable",
161 format!("{op} needs the native transport (build with --features native-transport)"),
162 )
163}
164
165#[cfg_attr(not(feature = "native-transport"), allow(unused_variables))]
168impl Transport for HostTransport {
169 fn sync(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
170 match self {
171 HostTransport::Null { .. } => Err(unavailable("sync")),
172 #[cfg(feature = "native-transport")]
173 HostTransport::Native(t) => t.sync(request),
174 }
175 }
176
177 fn remote_operation(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
178 match self {
179 HostTransport::Null { .. } => Err(unavailable("remoteOperation")),
180 #[cfg(feature = "native-transport")]
181 HostTransport::Native(t) => t.remote_operation(request),
182 }
183 }
184
185 fn realtime_sync(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
186 match self {
187 HostTransport::Null { .. } => Err(unavailable("realtimeSync")),
188 #[cfg(feature = "native-transport")]
189 HostTransport::Native(t) => t.realtime_sync(request),
190 }
191 }
192
193 fn download_segment(&mut self, request: &SegmentRequest) -> Result<Vec<u8>, TransportError> {
194 match self {
195 HostTransport::Null { .. } => Err(unavailable("downloadSegment")),
196 #[cfg(feature = "native-transport")]
197 HostTransport::Native(t) => t.download_segment(request),
198 }
199 }
200
201 fn supports_url_fetch(&self) -> bool {
202 match self {
203 HostTransport::Null { signed_urls, .. } => *signed_urls,
204 #[cfg(feature = "native-transport")]
205 HostTransport::Native(t) => t.signed_urls,
206 }
207 }
208
209 fn fetch_url(&mut self, url: &str) -> Result<Vec<u8>, TransportError> {
210 match self {
211 HostTransport::Null { .. } => Err(unavailable("fetchUrl")),
212 #[cfg(feature = "native-transport")]
213 HostTransport::Native(t) => t.fetch_url(url),
214 }
215 }
216
217 fn blob_upload(
218 &mut self,
219 blob_id: &str,
220 bytes: &[u8],
221 media_type: Option<&str>,
222 ) -> Result<(), TransportError> {
223 match self {
224 HostTransport::Null { .. } => Err(unavailable("blobUpload")),
225 #[cfg(feature = "native-transport")]
226 HostTransport::Native(t) => t.blob_upload(blob_id, bytes, media_type),
227 }
228 }
229
230 fn blob_download(&mut self, blob_id: &str) -> Result<BlobDownload, TransportError> {
231 match self {
232 HostTransport::Null { .. } => Err(unavailable("blobDownload")),
233 #[cfg(feature = "native-transport")]
234 HostTransport::Native(t) => t.blob_download(blob_id),
235 }
236 }
237
238 fn fetch_blob_url(&mut self, url: &str) -> Result<Vec<u8>, TransportError> {
239 match self {
240 HostTransport::Null { .. } => Err(unavailable("fetchBlobUrl")),
241 #[cfg(feature = "native-transport")]
242 HostTransport::Native(t) => t.fetch_blob_url(url),
243 }
244 }
245
246 fn blob_upload_grant(
247 &mut self,
248 blob_id: &str,
249 byte_length: u64,
250 media_type: Option<&str>,
251 ) -> Result<BlobUploadGrant, TransportError> {
252 match self {
253 HostTransport::Null { .. } => Ok(BlobUploadGrant::None),
256 #[cfg(feature = "native-transport")]
257 HostTransport::Native(t) => t.blob_upload_grant(blob_id, byte_length, media_type),
258 }
259 }
260
261 fn blob_put_url(
262 &mut self,
263 url: &str,
264 bytes: &[u8],
265 media_type: Option<&str>,
266 ) -> Result<(), TransportError> {
267 match self {
268 HostTransport::Null { .. } => Err(unavailable("blobPutUrl")),
269 #[cfg(feature = "native-transport")]
270 HostTransport::Native(t) => t.blob_put_url(url, bytes, media_type),
271 }
272 }
273
274 fn realtime_connect(&mut self) -> Result<(), TransportError> {
275 match self {
276 HostTransport::Null { .. } => Err(unavailable("realtimeConnect")),
277 #[cfg(feature = "native-transport")]
278 HostTransport::Native(t) => {
279 t.set_realtime_client_id(None);
280 t.realtime_connect()
281 }
282 }
283 }
284
285 fn realtime_connect_for_client(&mut self, client_id: &str) -> Result<(), TransportError> {
286 match self {
287 HostTransport::Null { .. } => Err(unavailable("realtimeConnect")),
288 #[cfg(feature = "native-transport")]
289 HostTransport::Native(t) => {
290 t.set_realtime_client_id(Some(client_id));
291 t.realtime_connect()
292 }
293 }
294 }
295
296 fn realtime_send(&mut self, text: &str) -> Result<(), TransportError> {
297 match self {
298 HostTransport::Null { .. } => Err(unavailable("realtimeSend")),
299 #[cfg(feature = "native-transport")]
300 HostTransport::Native(t) => t.realtime_send(text),
301 }
302 }
303
304 fn realtime_close(&mut self) -> Result<(), TransportError> {
305 match self {
306 HostTransport::Null { .. } => Ok(()),
307 #[cfg(feature = "native-transport")]
308 HostTransport::Native(t) => t.realtime_close(),
309 }
310 }
311}
312
313#[cfg(feature = "native-transport")]
314mod native {
315 use std::net::TcpStream;
327 use std::sync::atomic::{AtomicBool, Ordering};
328 use std::sync::{mpsc, Arc, Condvar, Mutex};
329 use std::thread::JoinHandle;
330 use std::time::Duration;
331
332 use polling::{Event, Events, Poller};
333 use tungstenite::stream::MaybeTlsStream;
334 use tungstenite::Message;
335
336 use super::{Inbound, InboundBuffer};
337 use crate::{
338 BlobDownload, BlobUploadGrant, RealtimeRound, RoundInbound, SegmentRequest, Transport,
339 TransportError,
340 };
341
342 struct SocketRegistration {
344 poller: Arc<Poller>,
345 stream: TcpStream,
346 }
347
348 impl Drop for SocketRegistration {
349 fn drop(&mut self) {
350 let _ = self.poller.delete(&self.stream);
351 }
352 }
353
354 struct SocketSender {
355 queue: mpsc::SyncSender<Outgoing>,
356 poller: Arc<Poller>,
357 }
358
359 struct Outgoing {
360 message: Message,
361 completed: mpsc::Sender<Result<(), TransportError>>,
362 }
363
364 const ROUND_TIMEOUT: Duration = Duration::from_secs(30);
368 #[derive(Default)]
375 pub(super) struct RoundChannel {
376 state: Mutex<RoundState>,
377 ready: Condvar,
378 }
379
380 #[derive(Default)]
381 struct RoundState {
382 round: RealtimeRound,
383 outcome: Option<Result<Vec<u8>, TransportError>>,
385 }
386
387 impl RoundChannel {
388 fn begin(&self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
392 let mut state = self.state.lock().expect("round lock");
393 state.outcome = None;
394 state.round.begin(request)
395 }
396
397 fn route_binary(&self, frame: &[u8]) -> Option<Vec<u8>> {
401 let mut state = self.state.lock().expect("round lock");
402 match state.round.route_binary(frame) {
403 Ok(RoundInbound::Delta(body)) => Some(body),
404 Ok(RoundInbound::RoundProgress) | Ok(RoundInbound::Ignored) => None,
405 Ok(RoundInbound::RoundComplete(bytes)) => {
406 state.outcome = Some(Ok(bytes));
407 self.ready.notify_all();
408 None
409 }
410 Err(error) => {
411 state.outcome = Some(Err(error));
412 self.ready.notify_all();
413 None
414 }
415 }
416 }
417
418 fn fail_in_flight(&self, error: TransportError) {
420 let mut state = self.state.lock().expect("round lock");
421 if state.round.in_flight() && state.outcome.is_none() {
422 state.round.abort();
423 state.outcome = Some(Err(error));
424 self.ready.notify_all();
425 }
426 }
427
428 fn wait(&self) -> Result<Vec<u8>, TransportError> {
430 let mut state = self.state.lock().expect("round lock");
431 let deadline = std::time::Instant::now() + ROUND_TIMEOUT;
432 while state.outcome.is_none() {
433 let now = std::time::Instant::now();
434 if now >= deadline {
435 state.round.abort();
436 return Err(TransportError::new(
437 "sync.transport_failed",
438 "realtime sync round timed out (§8.7)",
439 ));
440 }
441 let (guard, _timeout) = self
442 .ready
443 .wait_timeout(state, deadline - now)
444 .expect("round wait");
445 state = guard;
446 }
447 state.outcome.take().expect("outcome present")
448 }
449 }
450
451 fn http_err(op: &str, e: impl std::fmt::Display) -> TransportError {
452 TransportError::new("transport.failed", format!("{op}: {e}"))
453 }
454
455 fn is_would_block(e: &tungstenite::Error) -> bool {
457 matches!(
458 e,
459 tungstenite::Error::Io(io) if matches!(
460 io.kind(),
461 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
462 )
463 )
464 }
465
466 pub struct NativeTransport {
467 base_url: String,
468 ws_url: String,
469 headers: Vec<(String, String)>,
471 agent: ureq::Agent,
472 pub signed_urls: bool,
473 pub inbound: Arc<InboundBuffer>,
474 outgoing: Option<Box<SocketSender>>,
476 reader: Option<JoinHandle<()>>,
477 reader_stop: Arc<AtomicBool>,
478 round: Arc<RoundChannel>,
480 realtime_client_id: Option<String>,
481 }
482
483 fn derive_ws_url(base_url: &str) -> String {
484 let ws = if let Some(rest) = base_url.strip_prefix("https://") {
487 format!("wss://{rest}")
488 } else if let Some(rest) = base_url.strip_prefix("http://") {
489 format!("ws://{rest}")
490 } else {
491 base_url.to_owned()
492 };
493 let trimmed = ws.trim_end_matches('/');
494 format!("{trimmed}/realtime")
495 }
496
497 impl NativeTransport {
498 pub fn new(
499 base_url: &str,
500 config: &serde_json::Value,
501 notify: Option<Arc<dyn Fn() + Send + Sync>>,
502 ) -> Result<Self, String> {
503 let mut headers = Vec::new();
504 if let Some(map) = config.get("headers").and_then(|v| v.as_object()) {
505 for (k, v) in map {
506 if let Some(s) = v.as_str() {
507 headers.push((k.clone(), s.to_owned()));
508 }
509 }
510 }
511 let ws_url = config
512 .get("wsUrl")
513 .and_then(|v| v.as_str())
514 .map(str::to_owned)
515 .unwrap_or_else(|| derive_ws_url(base_url));
516 Ok(NativeTransport {
517 base_url: base_url.trim_end_matches('/').to_owned(),
518 ws_url,
519 headers,
520 agent: ureq::Agent::new_with_defaults(),
521 signed_urls: false,
522 inbound: Arc::new(match notify {
523 Some(notify) => InboundBuffer::with_notify(notify),
524 None => InboundBuffer::default(),
525 }),
526 outgoing: None,
527 reader: None,
528 reader_stop: Arc::new(AtomicBool::new(false)),
529 round: Arc::new(RoundChannel::default()),
530 realtime_client_id: None,
531 })
532 }
533
534 fn post_sync(&self, path: &str, body: &[u8]) -> Result<Vec<u8>, TransportError> {
535 let url = format!("{}{}", self.base_url, path);
536 let mut req = self
539 .agent
540 .post(&url)
541 .header("content-type", "application/vnd.syncular.sync.v2");
542 for (k, v) in &self.headers {
543 req = req.header(k.as_str(), v.as_str());
544 }
545 let resp = req.send(body).map_err(|e| http_err("POST", e))?;
546 read_body(resp)
547 }
548
549 fn post_operation(&self, body: &[u8]) -> Result<Vec<u8>, TransportError> {
550 let url = format!("{}/operations", self.base_url);
551 let mut req = self.agent.post(&url).header(
552 "content-type",
553 "application/vnd.syncular.operations.v1+json",
554 );
555 for (key, value) in &self.headers {
556 req = req.header(key.as_str(), value.as_str());
557 }
558 let response = req
559 .send(body)
560 .map_err(|error| http_err("POST operation", error))?;
561 read_body(response)
562 }
563
564 fn get_bytes(&self, url: &str, with_headers: bool) -> Result<Vec<u8>, TransportError> {
565 let mut req = self.agent.get(url);
566 if with_headers {
567 for (k, v) in &self.headers {
568 req = req.header(k.as_str(), v.as_str());
569 }
570 }
571 let resp = req.call().map_err(|e| http_err("GET", e))?;
572 read_body(resp)
573 }
574
575 pub fn set_headers(&mut self, headers: Vec<(String, String)>) {
576 self.headers = headers;
577 }
578
579 pub fn set_realtime_client_id(&mut self, client_id: Option<&str>) {
580 self.realtime_client_id = client_id.map(str::to_owned);
581 }
582
583 pub fn shutdown(&mut self) {
584 self.reader_stop.store(true, Ordering::SeqCst);
585 self.round.fail_in_flight(TransportError::new(
588 "sync.transport_failed",
589 "realtime disconnected mid-round (§8.7)",
590 ));
591 if let Some(outgoing) = &self.outgoing {
592 let _ = outgoing.poller.notify();
593 }
594 if let Some(handle) = self.reader.take() {
595 let _ = handle.join();
596 }
597 self.outgoing = None;
598 }
599
600 fn send_message(&mut self, message: Message) -> Result<(), TransportError> {
601 let result = (|| {
602 let Some(outgoing) = &self.outgoing else {
603 return Err(TransportError::new(
604 "transport.failed",
605 "realtime not connected",
606 ));
607 };
608 let (completed, result) = mpsc::channel();
609 outgoing
610 .queue
611 .try_send(Outgoing { message, completed })
612 .map_err(|_| {
613 TransportError::new("transport.failed", "realtime send queue unavailable")
614 })?;
615 outgoing
616 .poller
617 .notify()
618 .map_err(|e| http_err("ws wake", e))?;
619 result.recv_timeout(ROUND_TIMEOUT).map_err(|_| {
620 TransportError::new("transport.failed", "realtime send did not complete")
621 })?
622 })();
623 if result.is_err() {
626 self.shutdown();
627 }
628 result
629 }
630 }
631
632 fn read_body(resp: ureq::http::Response<ureq::Body>) -> Result<Vec<u8>, TransportError> {
633 resp.into_body()
634 .into_with_config()
635 .read_to_vec()
636 .map_err(|e| http_err("read", e))
637 }
638
639 impl Transport for NativeTransport {
640 fn sync(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
641 self.post_sync("/sync", request)
642 }
643
644 fn remote_operation(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
645 self.post_operation(request)
646 }
647
648 fn realtime_sync(&mut self, request: &[u8]) -> Result<Vec<u8>, TransportError> {
649 if self.outgoing.is_none() {
659 return self.post_sync("/sync", request);
660 }
661 let framed = self.round.begin(request)?;
662 if let Err(error) = self.send_message(Message::Binary(framed.into())) {
663 self.round.fail_in_flight(error);
664 }
665 self.round.wait()
666 }
667
668 fn download_segment(
669 &mut self,
670 request: &SegmentRequest,
671 ) -> Result<Vec<u8>, TransportError> {
672 let url = format!("{}/segments/{}", self.base_url, request.segment_id);
679 let mut req = self
680 .agent
681 .get(&url)
682 .header("x-syncular-scopes", &request.requested_scopes_json);
683 for (k, v) in &self.headers {
684 req = req.header(k.as_str(), v.as_str());
685 }
686 let resp = req.call().map_err(|e| http_err("GET segment", e))?;
687 read_body(resp)
688 }
689
690 fn supports_url_fetch(&self) -> bool {
691 self.signed_urls
692 }
693
694 fn fetch_url(&mut self, url: &str) -> Result<Vec<u8>, TransportError> {
695 self.get_bytes(url, false)
697 }
698
699 fn blob_upload(
700 &mut self,
701 blob_id: &str,
702 bytes: &[u8],
703 media_type: Option<&str>,
704 ) -> Result<(), TransportError> {
705 let url = format!("{}/blobs/{}", self.base_url, blob_id);
708 let mut req = self.agent.put(&url).header(
709 "content-type",
710 media_type.unwrap_or("application/octet-stream"),
711 );
712 for (k, v) in &self.headers {
713 req = req.header(k.as_str(), v.as_str());
714 }
715 req.send(bytes).map_err(|e| http_err("PUT blob", e))?;
716 Ok(())
717 }
718
719 fn blob_download(&mut self, blob_id: &str) -> Result<BlobDownload, TransportError> {
720 let url = format!("{}/blobs/{}", self.base_url, blob_id);
723 let mut req = self.agent.get(&url);
724 for (k, v) in &self.headers {
725 req = req.header(k.as_str(), v.as_str());
726 }
727 let resp = req.call().map_err(|e| {
728 let e = http_err("GET blob", e);
729 if e.code == "transport.failed" {
731 TransportError::new("blob.not_found", e.message)
732 } else {
733 e
734 }
735 })?;
736 let is_json = resp
739 .headers()
740 .get(ureq::http::header::CONTENT_TYPE)
741 .and_then(|value| value.to_str().ok())
742 .is_some_and(|value| value.contains("application/json"));
743 let body = read_body(resp)?;
744 if is_json {
745 if let Ok(parsed) = serde_json::from_slice::<serde_json::Value>(&body) {
746 if let Some(u) = parsed.get("url").and_then(|v| v.as_str()) {
747 return Ok(BlobDownload::Url {
748 url: u.to_owned(),
749 url_expires_at_ms: parsed
750 .get("urlExpiresAtMs")
751 .and_then(|v| v.as_i64()),
752 });
753 }
754 }
755 }
756 Ok(BlobDownload::Bytes(body))
757 }
758
759 fn fetch_blob_url(&mut self, url: &str) -> Result<Vec<u8>, TransportError> {
760 self.get_bytes(url, false)
762 }
763
764 fn blob_upload_grant(
765 &mut self,
766 blob_id: &str,
767 byte_length: u64,
768 media_type: Option<&str>,
769 ) -> Result<BlobUploadGrant, TransportError> {
770 let url = format!("{}/blobs/{}/upload-grant", self.base_url, blob_id);
771 let mut req = self
772 .agent
773 .post(&url)
774 .header("content-type", "application/json");
775 for (k, v) in &self.headers {
776 req = req.header(k.as_str(), v.as_str());
777 }
778 let body = serde_json::json!({
779 "byteLength": byte_length,
780 "mediaType": media_type,
781 });
782 let resp = req
783 .send(body.to_string())
784 .map_err(|e| http_err("POST upload-grant", e))?;
785 let grant_body = read_body(resp)?;
786 let parsed: serde_json::Value = serde_json::from_slice(&grant_body)
787 .map_err(|e| TransportError::new("transport.failed", format!("read grant: {e}")))?;
788 if let Some(u) = parsed.get("url").and_then(|v| v.as_str()) {
789 return Ok(BlobUploadGrant::Url {
790 url: u.to_owned(),
791 url_expires_at_ms: parsed.get("urlExpiresAtMs").and_then(|v| v.as_i64()),
792 });
793 }
794 if parsed.get("present").and_then(|v| v.as_bool()) == Some(true) {
795 return Ok(BlobUploadGrant::Present);
796 }
797 Ok(BlobUploadGrant::None)
798 }
799
800 fn blob_put_url(
801 &mut self,
802 url: &str,
803 bytes: &[u8],
804 media_type: Option<&str>,
805 ) -> Result<(), TransportError> {
806 let req = self.agent.put(url).header(
808 "content-type",
809 media_type.unwrap_or("application/octet-stream"),
810 );
811 req.send(bytes).map_err(|e| http_err("PUT blob url", e))?;
812 Ok(())
813 }
814
815 fn realtime_connect(&mut self) -> Result<(), TransportError> {
816 if self.outgoing.is_some() {
817 return Ok(());
818 }
819 use tungstenite::client::IntoClientRequest;
825 let mut url = url::Url::parse(&self.ws_url)
826 .map_err(|e| TransportError::new("transport.failed", format!("ws url: {e}")))?;
827 if let Some(client_id) = self.realtime_client_id.as_deref() {
828 let retained_query: Vec<(String, String)> = url
829 .query_pairs()
830 .filter(|(key, _)| key != "clientId")
831 .map(|(key, value)| (key.into_owned(), value.into_owned()))
832 .collect();
833 url.set_query(None);
834 url.query_pairs_mut()
835 .extend_pairs(retained_query)
836 .append_pair("clientId", client_id);
837 }
838 let mut request = url
839 .as_str()
840 .into_client_request()
841 .map_err(|e| TransportError::new("transport.failed", format!("ws url: {e}")))?;
842 {
843 let out = request.headers_mut();
844 for (k, v) in &self.headers {
845 if let (Ok(name), Ok(value)) = (
846 tungstenite::http::HeaderName::try_from(k.as_str()),
847 tungstenite::http::HeaderValue::try_from(v.as_str()),
848 ) {
849 out.insert(name, value);
850 }
851 }
852 }
853 let (mut ws, _resp) = tungstenite::connect(request)
854 .map_err(|e| TransportError::new("transport.failed", format!("ws connect: {e}")))?;
855 let stream = match ws.get_mut() {
856 MaybeTlsStream::Plain(stream) => stream.try_clone(),
857 MaybeTlsStream::Rustls(stream) => stream.get_ref().try_clone(),
858 _ => {
859 return Err(TransportError::new(
860 "transport.failed",
861 "unsupported websocket stream",
862 ))
863 }
864 }
865 .map_err(|e| http_err("ws clone", e))?;
866 stream
867 .set_nonblocking(true)
868 .map_err(|e| http_err("ws nonblocking", e))?;
869 let poller = Arc::new(Poller::new().map_err(|e| http_err("ws poller", e))?);
870 unsafe { poller.add(&stream, Event::readable(0)) }
872 .map_err(|e| http_err("ws register", e))?;
873 let registration = SocketRegistration {
874 poller: Arc::clone(&poller),
875 stream,
876 };
877 let (outgoing, queued) = mpsc::sync_channel::<Outgoing>(1);
878 self.outgoing = Some(Box::new(SocketSender {
879 queue: outgoing,
880 poller,
881 }));
882 self.reader_stop.store(false, Ordering::SeqCst);
883 let inbound = Arc::clone(&self.inbound);
884 let stop = Arc::clone(&self.reader_stop);
885 let round = Arc::clone(&self.round);
886 self.reader = Some(std::thread::spawn(move || {
887 let mut events = Events::new();
888 let mut pending: Option<mpsc::Sender<Result<(), TransportError>>> = None;
889 let failure = 'io: loop {
890 if stop.load(Ordering::SeqCst) {
891 let _ = ws.close(None);
892 let _ = ws.flush();
893 break TransportError::new(
894 "sync.transport_failed",
895 "realtime disconnected mid-round (§8.7)",
896 );
897 }
898 if pending.is_none() {
899 if let Ok(outgoing) = queued.try_recv() {
900 pending = Some(outgoing.completed);
901 if let Err(error) = ws.write(outgoing.message) {
903 if !is_would_block(&error) {
904 break http_err("ws write", error);
905 }
906 }
907 }
908 }
909 let mut drained = false;
911 for _ in 0..64 {
912 match ws.read() {
913 Ok(Message::Text(text)) => {
914 inbound.push(Inbound::Text(text.to_string()))
915 }
916 Ok(Message::Binary(bytes)) => {
917 if let Some(delta) = round.route_binary(&bytes) {
918 inbound.push(Inbound::Binary(delta));
919 }
920 }
921 Err(error) if is_would_block(&error) => {
922 drained = true;
923 break;
924 }
925 Ok(Message::Close(_)) => {
926 let _ = ws.flush();
927 break 'io TransportError::new(
928 "sync.transport_failed",
929 "realtime disconnected mid-round (§8.7)",
930 );
931 }
932 Err(_) => {
933 break 'io TransportError::new(
934 "sync.transport_failed",
935 "realtime disconnected mid-round (§8.7)",
936 );
937 }
938 Ok(_) => {}
939 }
940 }
941 let interest = match ws.flush() {
942 Ok(()) => {
943 if let Some(completed) = pending.take() {
944 let _ = completed.send(Ok(()));
945 }
946 Event::readable(0)
947 }
948 Err(error) if is_would_block(&error) => Event::all(0),
949 Err(error) => break http_err("ws flush", error),
950 };
951 if !drained {
952 continue;
953 }
954 if let Err(error) = registration.poller.modify(®istration.stream, interest) {
955 break http_err("ws rearm", error);
956 }
957 events.clear();
958 if let Err(error) = registration.poller.wait(&mut events, None) {
959 if error.kind() != std::io::ErrorKind::Interrupted {
960 break http_err("ws wait", error);
961 }
962 }
963 };
964 if let Some(completed) = pending {
965 let _ = completed.send(Err(failure.clone()));
966 }
967 while let Ok(outgoing) = queued.try_recv() {
968 let _ = outgoing.completed.send(Err(failure.clone()));
969 }
970 round.fail_in_flight(failure);
971 }));
972 Ok(())
973 }
974
975 fn realtime_send(&mut self, text: &str) -> Result<(), TransportError> {
976 self.send_message(Message::Text(text.to_owned().into()))
977 }
978
979 fn realtime_close(&mut self) -> Result<(), TransportError> {
980 self.shutdown();
981 Ok(())
982 }
983 }
984}