ranile/reqwasm

WebSockets impossible to use in Yew

voidpumpkin opened this issue · 4 comments

WebSockets impossible to use in Yew for me because I just did not manage to

  • store active connection in use_state/use_mut_ref in a way where i can deref/convert into a owned value. write.send(self) moves the connection instead of just taking a mutable ref
  • same problem as above with a struct component
  • when using futures::mspc:channel I almost made it work but with it I needed to keep poling for messages which imo already is bad

So either make the WebSocket struct clonable or make .send work with a &mut self instead of just self

Until then plain old web_sys works: https://gist.github.com/JIuH4/9b990f7da7d12419f3f3a4a01ee9a9d7

So either make the WebSocket struct clonable

It used to be that way but was changed because that caused bug(s). See 445e9a5

I'll try to get a write up working example soon

@voidpumpkin I wrote the following code, which compiles and works:

use std::ops::DerefMut;

use futures::{sink::SinkExt, stream::StreamExt};
use reqwasm::websocket::futures::WebSocket;
use reqwasm::websocket::Message;
use wasm_bindgen::UnwrapThrowExt;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let sender = use_mut_ref(|| None);
    let receiver = use_mut_ref(|| None);
    {
        use_effect_with_deps(
            move |_| {
                let ws = WebSocket::open("ws://localhost:8080/api/ws").unwrap_throw();
                let (tx, rx) = ws.split();
                sender.borrow_mut().replace(tx);
                receiver.borrow_mut().replace(rx);
                {
                    wasm_bindgen_futures::spawn_local(async move {
                        let mut sender = sender.borrow_mut();
                        let sender = sender.deref_mut().as_mut().unwrap();
                        match sender.send(Message::Text("msg".to_string())).await {
                            Ok(_) => gloo_console::log!("sent"),
                            Err(e) => gloo_console::error!(format!("sending error: {}", e)),
                        }
                    });
                }

                {
                    wasm_bindgen_futures::spawn_local(async move {
                        let mut receiver = receiver.borrow_mut();
                        let receiver = receiver.deref_mut().as_mut().unwrap();
                        while let Some(val) = receiver.next().await {
                            gloo_console::console!(format!("{:?}", val));
                        }
                    });
                }

                || {}
            },
            (),
        );
    }

    html! {"hello world"}
}

fn main() {
    yew::start_app::<App>();
}

There's some ugly aspects of it but those are because futures and Yew don't really play well together.

Well thx.
Personally i still prefer just using web-sys.
But this issue is closable now.

Here's a more cleaner and adaptable example:

use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::{future, pin_mut};
use futures::{sink::SinkExt, stream::StreamExt};
use reqwasm::websocket::futures::WebSocket;
use reqwasm::websocket::{Message, WebSocketError};
use std::cell::RefCell;
use std::ops::DerefMut;
use std::rc::Rc;
use wasm_bindgen::UnwrapThrowExt;
use yew::prelude::*;

#[derive(Clone)]
pub struct Context {
    sender: UnboundedSender<Message>,
    receiver: Rc<RefCell<UnboundedReceiver<Result<Message, WebSocketError>>>>,
}

impl PartialEq for Context {
    fn eq(&self, _other: &Self) -> bool {
        false
    }
}

#[function_component]
fn Consumer() -> Html {
    let ctx: Context = use_context().unwrap();
    {
        use_effect_with_deps(
            move |_| {
                let mut tx = ctx.sender;
                let rx = ctx.receiver;

                wasm_bindgen_futures::spawn_local(async move {
                    tx.send(Message::Text("message".to_string())).await.unwrap();
                });

                wasm_bindgen_futures::spawn_local(async move {
                    while let Some(m) = rx.borrow_mut().deref_mut().next().await {
                        gloo_console::console!(format!("{:?}", m));
                    }
                });

                || ()
            },
            (),
        )
    }
    html! {"hello world"}
}

#[function_component]
fn App() -> Html {
    let (mut read_tx, read_rx) = futures::channel::mpsc::unbounded();
    let (write_tx, write_rx) = futures::channel::mpsc::unbounded();

    let context = Context {
        sender: write_tx,
        receiver: Rc::new(RefCell::new(read_rx)),
    };

    {
        use_effect_with_deps(
            move |_| {
                let ws = WebSocket::open("ws://localhost:8080/api/ws").unwrap_throw();
                let (write, mut read) = ws.split();

                let fwd_writes = write_rx.map(Ok).forward(write);

                let fwd_reads = async move {
                    while let Some(m) = read.next().await {
                        read_tx.send(m).await.unwrap()
                    }
                };

                wasm_bindgen_futures::spawn_local(async move {
                    pin_mut!(fwd_writes, fwd_reads);
                    future::select(fwd_writes, fwd_reads).await;
                });

                || {}
            },
            (),
        );
    }

    html! {
        <ContextProvider<Context> {context}>
            <Consumer />
        </ContextProvider<Context>>
    }
}

fn main() {
    yew::start_app::<App>();
}