Skip to content

Commit

Permalink
fix(websocket-websys): Add support for different WASM environments
Browse files Browse the repository at this point in the history
Add support different WASM environments (such as workers, NodeJS)
that don't have a `window` global. This is done by introducing a
`WebContext` `enum` that abstracts and detects the `Window` vs the
`WorkerGlobalScope` API.
This is done due to the `web-sys` lack of interes to support this
(see discussion in
[this issue](rustwasm/wasm-bindgen#1046)).
  • Loading branch information
jsdanielh committed Nov 20, 2023
1 parent 338e467 commit f042762
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ libp2p-webrtc = { version = "0.6.1-alpha", path = "transports/webrtc" }
libp2p-webrtc-utils = { version = "0.1.0", path = "misc/webrtc-utils" }
libp2p-webrtc-websys = { version = "0.2.0-alpha", path = "transports/webrtc-websys" }
libp2p-websocket = { version = "0.43.0", path = "transports/websocket" }
libp2p-websocket-websys = { version = "0.3.0", path = "transports/websocket-websys" }
libp2p-websocket-websys = { version = "0.3.1", path = "transports/websocket-websys" }
libp2p-webtransport-websys = { version = "0.2.0", path = "transports/webtransport-websys" }
libp2p-yamux = { version = "0.45.0", path = "muxers/yamux" }
multiaddr = "0.18.1"
Expand Down
7 changes: 7 additions & 0 deletions transports/websocket-websys/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.3.1

- Add support for different WASM environments by introducing a `WebContext` that
detects and abstracts the `Window` vs the `WorkerGlobalScope` API. See [PR 4889].

[PR 4889]: https://github.com/libp2p/rust-libp2p/pull/4889

## 0.3.0


Expand Down
4 changes: 2 additions & 2 deletions transports/websocket-websys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "libp2p-websocket-websys"
edition = "2021"
rust-version = "1.60.0"
description = "WebSocket for libp2p under WASM environment"
version = "0.3.0"
version = "0.3.1"
authors = ["Vince Vasta <vince.vasta@gmail.com>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
Expand All @@ -20,7 +20,7 @@ parking_lot = "0.12.1"
send_wrapper = "0.6.0"
thiserror = "1.0.50"
wasm-bindgen = "0.2.88"
web-sys = { version = "0.3.65", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window"] }
web-sys = { version = "0.3.65", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window", "WorkerGlobalScope"] }

# Passing arguments to the docsrs builder in order to properly document cfg's.
# More information: https://docs.rs/about/builds#cross-compiling
Expand Down
37 changes: 23 additions & 14 deletions transports/websocket-websys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

//! Libp2p websocket transports built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html).
mod web_context;

use bytes::BytesMut;
use futures::task::AtomicWaker;
use futures::{future::Ready, io, prelude::*};
Expand All @@ -35,7 +37,17 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::{pin::Pin, task::Context, task::Poll};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{window, CloseEvent, Event, MessageEvent, WebSocket};
use web_sys::{CloseEvent, Event, MessageEvent, WebSocket};

use crate::web_context::WebContext;

/// Arbitrary, maximum amount we are willing to buffer before we throttle our user.
const MAX_BUFFER: usize = 1024 * 1024;

thread_local! {
/// Global web context for abstracting the `Window` vs the `WorkerGlobalScope` API
static GLOBAL_WEB_CONTEXT: WebContext = WebContext::new();
}

/// A Websocket transport that can be used in a wasm environment.
///
Expand All @@ -61,9 +73,6 @@ pub struct Transport {
_private: (),
}

/// Arbitrary, maximum amount we are willing to buffer before we throttle our user.
const MAX_BUFFER: usize = 1024 * 1024;

impl libp2p_core::Transport for Transport {
type Output = Connection;
type Error = Error;
Expand Down Expand Up @@ -300,13 +309,14 @@ impl Connection {
}
}
});
let buffered_amount_low_interval = window()
.expect("to have a window")
.set_interval_with_callback_and_timeout_and_arguments(
on_buffered_amount_low_closure.as_ref().unchecked_ref(),
100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
&Array::new(),
)
let buffered_amount_low_interval = GLOBAL_WEB_CONTEXT
.with(|g| {
g.set_interval_with_callback_and_timeout_and_arguments(
on_buffered_amount_low_closure.as_ref().unchecked_ref(),
100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
&Array::new(),
)
})
.expect("to be able to set an interval");

Self {
Expand Down Expand Up @@ -439,8 +449,7 @@ impl Drop for Connection {
.close_with_code_and_reason(GO_AWAY_STATUS_CODE, "connection dropped");
}

window()
.expect("to have a window")
.clear_interval_with_handle(self.inner.buffered_amount_low_interval)
GLOBAL_WEB_CONTEXT
.with(|g| g.clear_interval_with_handle(self.inner.buffered_amount_low_interval));
}
}
62 changes: 62 additions & 0 deletions transports/websocket-websys/src/web_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use wasm_bindgen::{prelude::*, JsCast};

/// Web context that abstract the window vs web worker API
#[derive(Debug)]
pub(crate) enum WebContext {
Window(web_sys::Window),
Worker(web_sys::WorkerGlobalScope),
}

impl WebContext {
pub(crate) fn new() -> Self {
#[wasm_bindgen]
extern "C" {
type Global;

#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;

#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
fn worker(this: &Global) -> JsValue;
}

let global: Global = js_sys::global().unchecked_into();

if !global.window().is_undefined() {
Self::Window(global.unchecked_into())
} else if !global.worker().is_undefined() {
Self::Worker(global.unchecked_into())
} else {
panic!("Only supported in a browser or web worker");
}
}

/// The `setInterval()` method.
pub(crate) fn set_interval_with_callback_and_timeout_and_arguments(
&self,
handler: &::js_sys::Function,
timeout: i32,
arguments: &::js_sys::Array,
) -> Result<i32, JsValue> {
match self {
WebContext::Window(w) => w.set_interval_with_callback_and_timeout_and_arguments(
handler,
timeout, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
arguments,
),
WebContext::Worker(w) => w.set_interval_with_callback_and_timeout_and_arguments(
handler,
timeout, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
arguments,
),
}
}

/// The `clearInterval()` method.
pub(crate) fn clear_interval_with_handle(&self, handle: i32) {
match self {
WebContext::Window(w) => w.clear_interval_with_handle(handle),
WebContext::Worker(w) => w.clear_interval_with_handle(handle),
}
}
}

0 comments on commit f042762

Please sign in to comment.