Skip to content

Commit b5baf8a

Browse files
committed
fix(websocket-websys): Add support for different WASM environments
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)).
1 parent 7f4ba69 commit b5baf8a

File tree

6 files changed

+96
-18
lines changed

6 files changed

+96
-18
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ libp2p-webrtc = { version = "0.6.1-alpha", path = "transports/webrtc" }
109109
libp2p-webrtc-utils = { version = "0.1.0", path = "misc/webrtc-utils" }
110110
libp2p-webrtc-websys = { version = "0.2.0-alpha", path = "transports/webrtc-websys" }
111111
libp2p-websocket = { version = "0.43.0", path = "transports/websocket" }
112-
libp2p-websocket-websys = { version = "0.3.0", path = "transports/websocket-websys" }
112+
libp2p-websocket-websys = { version = "0.3.1", path = "transports/websocket-websys" }
113113
libp2p-webtransport-websys = { version = "0.2.0", path = "transports/webtransport-websys" }
114114
libp2p-yamux = { version = "0.45.0", path = "muxers/yamux" }
115115
multiaddr = "0.18.0"

transports/websocket-websys/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.1
2+
3+
- Add support for different WASM environments by introducing a `WebContext` that
4+
detects and abstracts the `Window` vs the `WorkerGlobalScope` API. See [PR 4889].
5+
6+
[PR 4889]: https://github.com/libp2p/rust-libp2p/pull/4889
7+
18
## 0.3.0
29

310

transports/websocket-websys/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "libp2p-websocket-websys"
33
edition = "2021"
44
rust-version = "1.60.0"
55
description = "WebSocket for libp2p under WASM environment"
6-
version = "0.3.0"
6+
version = "0.3.1"
77
authors = ["Vince Vasta <[email protected]>"]
88
license = "MIT"
99
repository = "https://github.com/libp2p/rust-libp2p"
@@ -20,7 +20,7 @@ parking_lot = "0.12.1"
2020
send_wrapper = "0.6.0"
2121
thiserror = "1.0.50"
2222
wasm-bindgen = "0.2.88"
23-
web-sys = { version = "0.3.65", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window"] }
23+
web-sys = { version = "0.3.65", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window", "WorkerGlobalScope"] }
2424

2525
# Passing arguments to the docsrs builder in order to properly document cfg's.
2626
# More information: https://docs.rs/about/builds#cross-compiling

transports/websocket-websys/src/lib.rs

+23-14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
//! Libp2p websocket transports built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html).
2222
23+
mod web_context;
24+
2325
use bytes::BytesMut;
2426
use futures::task::AtomicWaker;
2527
use futures::{future::Ready, io, prelude::*};
@@ -35,7 +37,17 @@ use std::sync::atomic::{AtomicBool, Ordering};
3537
use std::sync::Mutex;
3638
use std::{pin::Pin, task::Context, task::Poll};
3739
use wasm_bindgen::{prelude::*, JsCast};
38-
use web_sys::{window, CloseEvent, Event, MessageEvent, WebSocket};
40+
use web_sys::{CloseEvent, Event, MessageEvent, WebSocket};
41+
42+
use crate::web_context::WebContext;
43+
44+
/// Arbitrary, maximum amount we are willing to buffer before we throttle our user.
45+
const MAX_BUFFER: usize = 1024 * 1024;
46+
47+
thread_local! {
48+
/// Global web context for abstracting the `Window` vs the `WorkerGlobalScope` API
49+
static GLOBAL_WEB_CONTEXT: WebContext = WebContext::new();
50+
}
3951

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

64-
/// Arbitrary, maximum amount we are willing to buffer before we throttle our user.
65-
const MAX_BUFFER: usize = 1024 * 1024;
66-
6776
impl libp2p_core::Transport for Transport {
6877
type Output = Connection;
6978
type Error = Error;
@@ -300,13 +309,14 @@ impl Connection {
300309
}
301310
}
302311
});
303-
let buffered_amount_low_interval = window()
304-
.expect("to have a window")
305-
.set_interval_with_callback_and_timeout_and_arguments(
306-
on_buffered_amount_low_closure.as_ref().unchecked_ref(),
307-
100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
308-
&Array::new(),
309-
)
312+
let buffered_amount_low_interval = GLOBAL_WEB_CONTEXT
313+
.with(|g| {
314+
g.set_interval_with_callback_and_timeout_and_arguments(
315+
on_buffered_amount_low_closure.as_ref().unchecked_ref(),
316+
100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
317+
&Array::new(),
318+
)
319+
})
310320
.expect("to be able to set an interval");
311321

312322
Self {
@@ -439,8 +449,7 @@ impl Drop for Connection {
439449
.close_with_code_and_reason(GO_AWAY_STATUS_CODE, "connection dropped");
440450
}
441451

442-
window()
443-
.expect("to have a window")
444-
.clear_interval_with_handle(self.inner.buffered_amount_low_interval)
452+
GLOBAL_WEB_CONTEXT
453+
.with(|g| g.clear_interval_with_handle(self.inner.buffered_amount_low_interval));
445454
}
446455
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use wasm_bindgen::{prelude::*, JsCast};
2+
3+
/// Web context that abstract the window vs web worker API
4+
#[derive(Debug)]
5+
pub(crate) enum WebContext {
6+
Window(web_sys::Window),
7+
Worker(web_sys::WorkerGlobalScope),
8+
}
9+
10+
impl WebContext {
11+
pub(crate) fn new() -> Self {
12+
#[wasm_bindgen]
13+
extern "C" {
14+
type Global;
15+
16+
#[wasm_bindgen(method, getter, js_name = Window)]
17+
fn window(this: &Global) -> JsValue;
18+
19+
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
20+
fn worker(this: &Global) -> JsValue;
21+
}
22+
23+
let global: Global = js_sys::global().unchecked_into();
24+
25+
if !global.window().is_undefined() {
26+
Self::Window(global.unchecked_into())
27+
} else if !global.worker().is_undefined() {
28+
Self::Worker(global.unchecked_into())
29+
} else {
30+
panic!("Only supported in a browser or web worker");
31+
}
32+
}
33+
34+
/// The `setInterval()` method.
35+
pub(crate) fn set_interval_with_callback_and_timeout_and_arguments(
36+
&self,
37+
handler: &::js_sys::Function,
38+
timeout: i32,
39+
arguments: &::js_sys::Array,
40+
) -> Result<i32, JsValue> {
41+
match self {
42+
WebContext::Window(w) => w.set_interval_with_callback_and_timeout_and_arguments(
43+
handler,
44+
timeout, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
45+
arguments,
46+
),
47+
WebContext::Worker(w) => w.set_interval_with_callback_and_timeout_and_arguments(
48+
handler,
49+
timeout, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time.
50+
arguments,
51+
),
52+
}
53+
}
54+
55+
/// The `clearInterval()` method.
56+
pub(crate) fn clear_interval_with_handle(&self, handle: i32) {
57+
match self {
58+
WebContext::Window(w) => w.clear_interval_with_handle(handle),
59+
WebContext::Worker(w) => w.clear_interval_with_handle(handle),
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)