Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventSource in WASM module #286

Open
justindthomas opened this issue Jan 15, 2023 · 9 comments
Open

EventSource in WASM module #286

justindthomas opened this issue Jan 15, 2023 · 9 comments
Labels
bug Something isn't working

Comments

@justindthomas
Copy link

Hi there. I'm trying to get the EventSource functionality to work from an embedded WASM module in a SvelteKit page. I've followed the guidance as closely as I can, but can't get it to actually attempt to even make a network call out - it just immediately jumps to "event source closed".

Here's what I have in the module code:

use gloo_net::eventsource::futures::{EventSource};
use futures::{stream, StreamExt};

 fn init_sse() {
    let mut es = EventSource::new("http://localhost:5173/events").unwrap();
    let stream_1 = es.subscribe("ping").unwrap();
    let stream_2 = es.subscribe("an-event-type").unwrap();
                     
    spawn_local(async move {
        let mut all_streams = stream::select(stream_1, stream_2);
        while let Some(Ok((event_type, msg))) = all_streams.next().await {
            log("received event")
        }
        log("event source closed");
    })
}

My event source is running on Rocket:

#[get("/events")]
fn stream() -> EventStream![] {
    EventStream! {
        let mut interval = time::interval(Duration::from_secs(5));

        loop {
            yield Event::data("hello there").event("ping");
            interval.tick().await;
        }
    }
}

I can see the data there if I just curl it. I also set up a proxy in Vite to facilitate the connection from the WASM module (and to avoid CORS problems)

proxy: {
    '/events': {
        target: 'http://ipaddress:8010/',
        changeOrigin: true,
        secure: false,
        ws: false
    }
}

That works fine and I can curl through that to the Rocket backend as expected.

Then in my Svelte page, all I'm doing is:

import init_wasm, {
	init_sse,
} from 'wasm'

. . .
function handleInitSse(event: any) {
	init_sse()
}

...and linking that up to a button that calls it when I click it. When I click the button, I get the immediate "closed" message in my console log, but no attempt at all by the module to make a network call out to the proxied Rocket server to open the EventSource.

What am I missing? Network connectivity from the WASM module is otherwise okay - I have several Request::post and Request::get operations via gloo_net that work fine.

@justindthomas justindthomas added the bug Something isn't working label Jan 15, 2023
@justindthomas
Copy link
Author

I made some progress this evening and discovered that it looks like my browsers (both Firefox and Chrome) are actively blocking the EventSource call from the wasm component, but are okay with it when it's just included in the page's JavaScript. Firefox is forthcoming about that and includes the attempt with a red "blocked" indicator. Chrome doesn't act like it sees anything at all in the Network panel. Here's an image showing the two calls (successful from JavaScript, and blocked from wasm) in Firefox.

events

It's not clear to me if the Vite wasm plugin might be part of the problem.

@peteringram0
Copy link

@justindthomas I see this is 3 weeks old. Did you get it working at all? I am seeing the same as you have described above. I suspect it maybe an issue with futures lib tho.

@justindthomas
Copy link
Author

@peteringram0 No luck yet. I've built around EventSource in JavaScript for now. I'd like to move that to the WASM module when I can, though.

@peteringram0
Copy link

@justindthomas You can use web_sys directly if you just need to listen for the messages but ideally this lib will be fixed as its a nice clean way to subscribe to events:

let event_source = web_sys::EventSource::new_with_event_source_init_dict(
    "url", 
    web_sys::EventSourceInit::new().with_credentials(true)
).unwrap();

let closure = Closure::wrap(Box::new(move |m: MessageEvent| {
    log!("Message received !! {:?}", m.data());
}) as Box<dyn FnMut(MessageEvent)>);
    
event_source.add_event_listener_with_callback("message",closure.as_ref().unchecked_ref()).unwrap();
let closure = closure.forget();

// Depeding on framework you need some way to clear refs
leptos::on_cleanup(cx.clone(), move || drop(closure));

@Philipp-Sc
Copy link

Today, I ran into the same bug. @peteringram0 thanks a lot for providing the web_sys solution, that did the trick for me.

@Philipp-Sc
Copy link

Philipp-Sc commented Oct 14, 2023

It looks like the web_sys solution does not work on mobile browsers. The request gets blocked, using plain javascript works though.

Edit: I narrowed it down, it looks like a CORS issue with my setup. (works with Firefox but not Chrome)

Edit2: Firefox supports authentication for event sources using user:password@url while chrome blocks the requests.

@peteringram0
Copy link

@Philipp-Sc For my own project i decided to use a token within the URL params due to browser support shortly after i left that snippet.

@justindthomas
Copy link
Author

@Philipp-Sc for authentication, I went the route of having the server send a UUID on connection setup that the client then uses to authenticate the stream via a separate POST request. This avoids keeping any authentication data in the URL at all.

@abdulmelikbekmez
Copy link

I also encountered the same problem. I was getting a CORS error when the code was like this.

    let mut es = EventSource::new("http://127.0.0.1:8000/telemetry").unwrap();
    let mut s = es.subscribe("PUT").unwrap();

    spawn_local(async move {
        log::debug!("event source started");
        while let Some(msg) = s.next().await {
            log::info!("new message arrived ");
            match msg {
                Ok((s, e)) => log::info!("msg => {:?} | event => {:?}", s, e),
                Err(e) => log::error!("ERROR!: {}", e),
            }
        }
        log::info!("event listener closed!!!");
    });

After moving the EventSource object creation process into the async task spawner, the problem disappeared.

    spawn_local(async move {
        let mut es = EventSource::new("http://127.0.0.1:8000/telemetry").unwrap();
        let mut s = es.subscribe("PUT").unwrap();

        log::debug!("event source started");
        while let Some(msg) = s.next().await {
            log::info!("new message arrived ");
            match msg {
                Ok((s, e)) => log::info!("msg => {:?} | event => {:?}", s, e),
                Err(e) => log::error!("ERROR!: {}", e),
            }
        }
        log::info!("event listener closed!!!");
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants