workflow_websocket/client/
wasm.rs1use super::{
2 ConnectOptions, ConnectResult, Handshake, Resolver, WebSocketConfig,
3 bindings::WebSocket as W3CWebSocket,
4 error::Error,
5 message::{Ack, Message},
6 result::Result,
7};
8use futures::{FutureExt, select, select_biased};
9use js_sys::{ArrayBuffer, Uint8Array};
10use std::ops::Deref;
11use std::sync::{
12 Arc, Mutex,
13 atomic::{AtomicBool, Ordering},
14};
15use wasm_bindgen::JsCast;
16use web_sys::{
17 CloseEvent as WsCloseEvent, ErrorEvent as WsErrorEvent, MessageEvent as WsMessageEvent,
18};
19use workflow_core::runtime::*;
20use workflow_core::{
21 channel::{Channel, DuplexChannel, Sender, oneshot, unbounded},
22 task::spawn,
23};
24use workflow_log::*;
25use workflow_wasm::callback::*;
26
27impl TryFrom<WsMessageEvent> for Message {
28 type Error = Error;
29
30 fn try_from(event: WsMessageEvent) -> std::result::Result<Self, Self::Error> {
31 match event.data() {
32 data if data.is_instance_of::<ArrayBuffer>() => {
33 let buffer = Uint8Array::new(data.unchecked_ref());
34 Ok(Message::Binary(buffer.to_vec()))
35 }
36 data if data.is_string() => match data.as_string() {
37 Some(text) => Ok(Message::Text(text)),
38 None => Err(Error::DataEncoding),
39 },
40 _ => Err(Error::DataType),
41 }
42 }
43}
44
45#[derive(Clone)]
46pub struct WebSocket(W3CWebSocket);
47unsafe impl Send for WebSocket {}
48unsafe impl Sync for WebSocket {}
49impl Deref for WebSocket {
50 type Target = W3CWebSocket;
51 fn deref(&self) -> &W3CWebSocket {
52 &self.0
53 }
54}
55
56impl WebSocket {
57 #[allow(dead_code)]
58 const CONNECTING: u16 = W3CWebSocket::CONNECTING;
59 #[allow(dead_code)]
60 const OPEN: u16 = W3CWebSocket::OPEN;
61 #[allow(dead_code)]
62 const CLOSING: u16 = W3CWebSocket::CLOSING;
63 #[allow(dead_code)]
64 const CLOSED: u16 = W3CWebSocket::CLOSED;
65
66 #[allow(dead_code)]
67 pub fn new(url: &str) -> Result<Self> {
68 Ok(WebSocket(W3CWebSocket::new(url)?))
69 }
70
71 pub fn new_with_config(url: &str, config: &WebSocketConfig) -> Result<Self> {
72 Ok(WebSocket(W3CWebSocket::new_with_config(url, config)?))
73 }
74
75 fn cleanup(&self) {
76 self.set_onopen(None);
77 self.set_onclose(None);
78 self.set_onerror(None);
79 self.set_onmessage(None);
80 }
81}
82
83impl From<W3CWebSocket> for WebSocket {
84 fn from(ws: W3CWebSocket) -> Self {
85 WebSocket(ws)
86 }
87}
88
89#[derive(Default)]
90struct Settings {
91 default_url: Option<String>,
93 current_url: Option<String>,
95}
96
97#[allow(dead_code)]
98struct Inner {
99 ws: WebSocket,
100 callbacks: CallbackMap,
101}
102
103unsafe impl Send for Inner {}
104unsafe impl Sync for Inner {}
105
106pub struct WebSocketInterface {
110 inner: Arc<Mutex<Option<Inner>>>,
111 settings: Arc<Mutex<Settings>>,
112 config: Mutex<WebSocketConfig>,
113 reconnect: AtomicBool,
114 is_connected: AtomicBool,
115 event_channel: Channel<Message>,
116 sender_channel: Channel<(Message, Ack)>,
117 receiver_channel: Channel<Message>,
118 dispatcher_shutdown: DuplexChannel,
119}
120
121impl WebSocketInterface {
122 pub fn new(
125 url: Option<&str>,
126 config: Option<WebSocketConfig>,
127 sender_channel: Channel<(Message, Ack)>,
128 receiver_channel: Channel<Message>,
129 ) -> Result<WebSocketInterface> {
130 sanity_checks()?;
131
132 let settings = Settings {
133 default_url: url.map(String::from),
134 ..Default::default()
135 };
136
137 let iface = WebSocketInterface {
138 inner: Arc::new(Mutex::new(None)),
139 settings: Arc::new(Mutex::new(settings)),
140 config: Mutex::new(config.unwrap_or_default()),
141 sender_channel,
142 receiver_channel,
143 event_channel: Channel::unbounded(),
144 reconnect: AtomicBool::new(true),
145 is_connected: AtomicBool::new(false),
146 dispatcher_shutdown: DuplexChannel::unbounded(),
147 };
148
149 Ok(iface)
150 }
151
152 pub fn default_url(self: &Arc<Self>) -> Option<String> {
154 self.settings.lock().unwrap().default_url.clone()
155 }
156
157 pub fn current_url(self: &Arc<Self>) -> Option<String> {
159 self.settings.lock().unwrap().current_url.clone()
160 }
161
162 pub fn set_default_url(self: &Arc<Self>, url: &str) {
164 self.settings
165 .lock()
166 .unwrap()
167 .default_url
168 .replace(url.to_string());
169 }
170
171 pub fn set_current_url(self: &Arc<Self>, url: &str) {
173 self.settings
174 .lock()
175 .unwrap()
176 .current_url
177 .replace(url.to_string());
178 }
179
180 pub fn is_connected(self: &Arc<Self>) -> bool {
182 self.is_connected.load(Ordering::SeqCst)
183 }
184
185 fn resolver(&self) -> Option<Arc<dyn Resolver>> {
186 self.config.lock().unwrap().resolver.clone()
187 }
188
189 fn handshake(&self) -> Option<Arc<dyn Handshake>> {
190 self.config.lock().unwrap().handshake.clone()
191 }
192
193 pub fn configure(&self, config: WebSocketConfig) {
195 *self.config.lock().unwrap() = config;
196 }
197
198 async fn resolve_url(self: &Arc<Self>, options: &ConnectOptions) -> Result<String> {
199 let url = if let Some(url) = options.url.as_ref().or(self.default_url().as_ref()) {
200 url.clone()
201 } else {
202 match self.resolver() {
203 Some(resolver) => resolver.resolve_url().await?,
204 _ => {
205 return Err(Error::MissingUrl);
206 }
207 }
208 };
209 self.set_current_url(&url);
210 Ok(url)
211 }
212
213 pub async fn connect(self: &Arc<Self>, options: ConnectOptions) -> ConnectResult<Error> {
217 let (connect_trigger, connect_listener) = oneshot::<Result<()>>();
218
219 let connect_trigger = Arc::new(Mutex::new(Some(connect_trigger)));
220 self.connect_impl(options.clone(), connect_trigger).await?;
221
222 match options.block_async_connect {
223 true => match connect_listener.recv().await? {
224 Ok(_) => Ok(None),
225 Err(e) => Err(e),
226 },
227 false => Ok(Some(connect_listener)),
228 }
229 }
230
231 fn retry_connect_impl(
232 self: Arc<Self>,
233 options: ConnectOptions,
234 connect_trigger: Arc<Mutex<Option<Sender<Result<()>>>>>,
235 ) -> futures::future::BoxFuture<'static, Result<()>> {
236 Box::pin(async move { self.connect_impl(options, connect_trigger).await })
237 as futures::future::BoxFuture<'static, Result<()>>
238 }
239
240 async fn connect_impl(
241 self: &Arc<Self>,
242 options: ConnectOptions,
243 connect_trigger: Arc<Mutex<Option<Sender<Result<()>>>>>,
244 ) -> Result<()> {
245 if self.inner.lock().unwrap().is_some() {
246 log_warn!("WebSocket::connect() called while already initialized");
247
248 return Err(Error::AlreadyInitialized);
249 }
250
251 self.reconnect.store(true, Ordering::SeqCst);
252
253 let url = match self.resolve_url(&options).await {
254 Ok(url) => url,
255 Err(err) => {
256 log_trace!("WebSocket unable to resolve URL: {err}");
257 let self_ = self.clone();
258
259 if options.strategy.is_fallback() {
260 self.reconnect.store(false, Ordering::SeqCst);
261
262 return Err(err);
268 }
269
270 let connect_trigger_ = connect_trigger.clone();
271 spawn(async move {
272 if self_.reconnect.load(Ordering::SeqCst) {
274 workflow_core::task::sleep(
275 options
276 .retry_interval
277 .unwrap_or(std::time::Duration::from_millis(1000)),
278 )
279 .await;
280 if self_.reconnect.load(Ordering::SeqCst) {
282 self_
283 .retry_connect_impl(options, connect_trigger_)
284 .await
285 .ok();
286 }
287 }
288 });
289
290 return Ok(());
291 }
292 };
293
294 let mut inner = self.inner.lock().unwrap();
295
296 let ws = WebSocket::new_with_config(&url, &self.config.lock().unwrap())?;
297 ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
298
299 let event_sender_ = self.event_channel.sender.clone();
301 let onmessage = callback!(move |event: WsMessageEvent| {
302 let msg: Message = event.try_into().expect("MessageEvent Error");
303 event_sender_.try_send(msg).unwrap_or_else(|err| {
304 log_trace!("WebSocket unable to try_send() `message` to event channel: `{err}`")
305 });
306 });
307 ws.set_onmessage(Some(onmessage.as_ref()));
308
309 let onerror = callback!(move |_event: WsErrorEvent| {
311 });
313 ws.set_onerror(Some(onerror.as_ref()));
314
315 let event_sender_ = self.event_channel.sender.clone();
317 let onopen = callback!(move || {
318 event_sender_.try_send(Message::Open).unwrap_or_else(|err| {
319 log_trace!("WebSocket unable to try_send() `open` to event channel: `{err}`")
320 });
321 });
322 ws.set_onopen(Some(onopen.as_ref()));
323
324 let event_sender_ = self.event_channel.sender.clone();
326 let onclose = callback!(move |_event: WsCloseEvent| {
327 event_sender_
329 .try_send(Message::Close)
330 .unwrap_or_else(|err| {
331 log_trace!("WebSocket unable to try_send() `close` to event channel: `{err}`")
332 });
333 });
334 ws.set_onclose(Some(onclose.as_ref()));
335
336 let callbacks = CallbackMap::new();
337 callbacks.retain(onmessage)?;
338 callbacks.retain(onerror)?;
339 callbacks.retain(onopen)?;
340 callbacks.retain(onclose)?;
341
342 *inner = Some(Inner {
343 ws: ws.clone(),
344 callbacks,
345 });
346
347 let self_ = self.clone();
348 spawn(async move {
349 self_
350 .dispatcher_task(&ws, options.clone(), connect_trigger.clone())
351 .await
352 .unwrap_or_else(|err| log_trace!("WebSocket error: {err}"));
353 if self_.reconnect.load(Ordering::SeqCst) {
355 workflow_core::task::sleep(
356 options
357 .retry_interval
358 .unwrap_or(std::time::Duration::from_millis(1000)),
359 )
360 .await;
361 if self_.reconnect.load(Ordering::SeqCst) {
363 self_.reconnect(options, connect_trigger).await.ok();
364 }
365 }
366 });
367
368 Ok(())
369 }
370
371 fn ws(self: &Arc<Self>) -> Option<WebSocket> {
372 self.inner
373 .lock()
374 .expect("WebSocket:: inner lock failure")
375 .as_ref()
376 .map(|inner| inner.ws.clone())
377 }
378
379 #[allow(dead_code)]
382 pub fn try_send(self: &Arc<Self>, message: &Message) -> Result<()> {
383 match self.ws() {
384 Some(ws) => {
385 ws.try_send(message)?;
386 Ok(())
387 }
388 _ => Err(Error::NotConnected),
389 }
390 }
391
392 async fn handshake_impl(self: &Arc<Self>, ws: &WebSocket) -> Result<()> {
393 if let Some(handshake) = self.handshake() {
394 let (sender_tx, sender_rx) = unbounded();
395 let (receiver_tx, receiver_rx) = unbounded();
396 let (accept_tx, accept_rx) = oneshot();
397
398 spawn(async move {
399 accept_tx
400 .send(handshake.handshake(&sender_tx, &receiver_rx).await)
401 .await
402 .unwrap_or_else(|err| {
403 log_trace!("WebSocket handshake unable to send completion: `{}`", err)
404 });
405 });
406
407 loop {
408 select_biased! {
409 result = accept_rx.recv().fuse() => {
410 return result?;
411 },
412 msg = sender_rx.recv().fuse() => {
413 if let Ok(msg) = msg {
414 ws.try_send(&msg)?;
415 }
416 },
417 msg = self.event_channel.recv().fuse() => {
418 if let Ok(msg) = msg {
419 receiver_tx.send(msg).await?;
420 }
421 }
422 }
423 }
424 }
425
426 Ok(())
427 }
428
429 async fn dispatcher_task(
430 self: &Arc<Self>,
431 ws: &WebSocket,
432 options: ConnectOptions,
433 connect_trigger: Arc<Mutex<Option<Sender<Result<()>>>>>,
434 ) -> Result<()> {
435 'outer: loop {
436 select! {
437 _ = self.dispatcher_shutdown.request.receiver.recv().fuse() => {
438 break 'outer;
439 },
440 msg = self.event_channel.recv().fuse() => {
441 match msg {
442 Ok(msg) => {
443 match msg {
444 Message::Binary(_) | Message::Text(_) => {
445 self.receiver_channel.sender.send(msg).await.unwrap();
446 },
447 Message::Open => {
448 if let Err(err) = self.handshake_impl(ws).await {
451 log_info!("WebSocket handshake negotiation error: {err}");
452
453 if options.strategy.is_fallback() {
454 self.reconnect.store(false, Ordering::SeqCst);
455 }
456
457 let connect_trigger = connect_trigger.lock().unwrap().take();
458 if let Some(connect_trigger) = connect_trigger {
459 connect_trigger.send(Err(err)).await.ok();
460 }
461
462 return Err(Error::NegotiationFailure);
463 }
464
465 self.is_connected.store(true, Ordering::SeqCst);
466
467 let connect_trigger = connect_trigger.lock().unwrap().take();
468 if let Some(connect_trigger) = connect_trigger {
469 connect_trigger.send(Ok(())).await.ok();
470 }
471
472 self.receiver_channel.sender.send(msg).await.unwrap();
473 },
474 Message::Close => {
475 if let Some(inner) = self.inner.lock().unwrap().take() {
478 inner.ws.cleanup();
479 }
480
481 if self.is_connected.load(Ordering::SeqCst) {
482 self.is_connected.store(false, Ordering::SeqCst);
483 self.receiver_channel.sender.send(msg).await.unwrap();
484 } else if options.strategy.is_fallback() && options.block_async_connect {
485 self.reconnect.store(false, Ordering::SeqCst);
488
489 let connect_trigger = connect_trigger.lock().unwrap().take();
490 if let Some(connect_trigger) = connect_trigger {
491 connect_trigger.send(Err(Error::Connect(self.current_url().unwrap()))).await.ok();
492 }
493 }
494
495 break 'outer;
496 }
497 }
498 }
499 Err(err) => {
500 log_error!("WebSocket dispatcher channel error: {err}");
501 }
502 }
503 },
504 msg = self.sender_channel.receiver.recv().fuse() => {
505
506 if let Ok((msg, ack)) = msg {
507
508 if let Some(ack) = ack {
513 let result = ws
514 .try_send(&msg)
515 .map(Arc::new)
516 .map_err(Arc::new);
517 ack.send(result).await.unwrap_or_else(|err| {
518 log_trace!("WebSocket error producing message ack {:?}", err)
519 });
520 } else {
521 ws.try_send(&msg).unwrap_or_else(|err| {
522 log_trace!("WebSocket unable to send `raw ws` message: `{err}`")
523 });
524 }
525 }
526 }
527 }
528 }
529
530 Ok(())
531 }
532
533 async fn _shutdown(self: &Arc<Self>) -> Result<()> {
534 self.dispatcher_shutdown
535 .signal(())
536 .await
537 .map_err(|_| Error::DispatcherSignal)?;
538
539 Ok(())
540 }
541
542 pub async fn close(self: &Arc<Self>) -> Result<()> {
545 if let Some(inner) = self.inner.lock().unwrap().take() {
546 inner.ws.cleanup();
547 inner.ws.close_if_open()?;
548 }
549
550 if self.is_connected.load(Ordering::SeqCst) {
551 self.event_channel.try_send(Message::Close)?;
552 }
553
554 Ok(())
555 }
556
557 async fn reconnect(
558 self: &Arc<Self>,
559 options: ConnectOptions,
560 connect_trigger: Arc<Mutex<Option<Sender<Result<()>>>>>,
561 ) -> Result<()> {
562 self.close().await?;
563
564 self.clone()
565 .retry_connect_impl(options, connect_trigger)
566 .await?;
567
568 Ok(())
569 }
570
571 pub async fn disconnect(self: &Arc<Self>) -> Result<()> {
573 self.reconnect.store(false, Ordering::SeqCst);
574 self.close().await.ok();
575 Ok(())
576 }
577
578 pub fn trigger_abort(self: &Arc<Self>) -> Result<()> {
581 if self.is_connected.load(Ordering::SeqCst)
582 && let Some(ws) = self.ws()
583 {
584 ws.close_if_open()?;
585 }
586 Ok(())
587 }
588}
589
590impl Drop for WebSocketInterface {
591 fn drop(&mut self) {}
592}
593
594trait TrySendMessage {
595 fn try_send(&self, message: &Message) -> Result<()>;
596}
597
598impl TrySendMessage for WebSocket {
599 fn try_send(&self, message: &Message) -> Result<()> {
600 match message {
601 Message::Binary(data) => {
602 if is_cross_origin_isolated() {
603 let array_buffer: ArrayBuffer = ArrayBuffer::new(data.len() as u32);
605 let uint8_array = Uint8Array::new(&array_buffer);
606 uint8_array.copy_from(&data[..]);
607 self.send_with_array_buffer(&array_buffer)
608 .map_err(|e| e.into())
609 } else {
610 self.send_with_u8_array(data).map_err(|e| e.into())
611 }
612 }
613 Message::Text(text) => self.send_with_str(text).map_err(|e| e.into()),
614 _ => {
615 panic!("WebSocket trying to convert unsupported message type: `{message:?}`");
616 }
617 }
618 }
619}
620
621fn w3c_websocket_available() -> Result<bool> {
622 Ok(js_sys::Reflect::get(&js_sys::global(), &"WebSocket".into())
623 .map(|v| !v.is_falsy())
624 .unwrap_or(false))
625}
626
627fn sanity_checks() -> Result<()> {
628 if !w3c_websocket_available()? {
629 if is_node() {
630 log_info!("");
631 log_info!("+------------------------------------------------------------");
632 log_info!("|");
633 log_info!("| w3c websocket is not available");
634 log_info!("|");
635 log_info!("| Please include `WebSocket` module as a project dependency");
636 log_info!("| and add the following line to your Node.js script:");
637 log_info!("|");
638 log_info!("| `globalThis.WebSocket = require(\"websocket\").w3cwebsocket;`");
639 log_info!("|");
640 log_info!("| (or use any other w3c-compatible module)");
641 log_info!("|");
642 log_info!("+------------------------------------------------------------");
643 log_info!("");
644 } else {
645 log_info!("");
646 log_error!("w3c websocket is not available");
647 log_info!("");
648 }
649 panic!("w3c websocket is not available");
650 }
651 Ok(())
652}