web: Use gloo_net instead of manually using web_sys

This relieves some headaches with on connect callback and spawning of the
sending async task, since some SWFs like to send data before the connect event
is called.
This commit is contained in:
sleepycatcoding 2023-09-08 02:14:17 +03:00 committed by Nathan Adams
parent a067e9a5e8
commit d3765027f0
3 changed files with 65 additions and 56 deletions

32
Cargo.lock generated
View File

@ -2031,6 +2031,36 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "gloo-net"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4"
dependencies = [
"futures-channel",
"futures-core",
"futures-sink",
"gloo-utils",
"http",
"js-sys",
"pin-project",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "gloo-utils"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.12.3" version = "0.12.3"
@ -4217,8 +4247,10 @@ dependencies = [
"base64", "base64",
"chrono", "chrono",
"console_error_panic_hook", "console_error_panic_hook",
"futures-util",
"generational-arena", "generational-arena",
"getrandom", "getrandom",
"gloo-net",
"js-sys", "js-sys",
"ruffle_core", "ruffle_core",
"ruffle_render", "ruffle_render",

View File

@ -50,6 +50,8 @@ serde = { version = "1.0.188", features = ["derive"] }
thiserror = "1.0" thiserror = "1.0"
base64 = "0.21.4" base64 = "0.21.4"
async-channel = "1.9.0" async-channel = "1.9.0"
futures-util = { version = "0.3.28", features = ["sink"] }
gloo-net = { version = "0.4.0", default-features = false, features = ["websocket"] }
[dependencies.ruffle_core] [dependencies.ruffle_core]
path = "../core" path = "../core"
@ -64,5 +66,5 @@ features = [
"ChannelMergerNode", "ChannelSplitterNode", "ClipboardEvent", "DataTransfer", "Element", "Event", "ChannelMergerNode", "ChannelSplitterNode", "ClipboardEvent", "DataTransfer", "Element", "Event",
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement", "EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent", "HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent",
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ProgressEvent", "WebSocket", "MessageEvent", "BinaryType", "ErrorEvent", "CloseEvent", "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window",
] ]

View File

@ -1,6 +1,8 @@
//! Navigator backend for web //! Navigator backend for web
use crate::WebSocketProxy; use crate::WebSocketProxy;
use async_channel::Receiver; use async_channel::Receiver;
use futures_util::{SinkExt, StreamExt};
use gloo_net::websocket::{futures::WebSocket, Message};
use js_sys::{Array, ArrayBuffer, Uint8Array}; use js_sys::{Array, ArrayBuffer, Uint8Array};
use ruffle_core::backend::navigator::{ use ruffle_core::backend::navigator::{
async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod, async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod,
@ -17,12 +19,11 @@ use tracing_subscriber::layer::Layered;
use tracing_subscriber::Registry; use tracing_subscriber::Registry;
use tracing_wasm::WASMLayer; use tracing_wasm::WASMLayer;
use url::{ParseError, Url}; use url::{ParseError, Url};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{ use web_sys::{
window, Blob, BlobPropertyBag, CloseEvent, ErrorEvent, HtmlFormElement, HtmlInputElement, window, Blob, BlobPropertyBag, HtmlFormElement, HtmlInputElement, Request as WebRequest,
MessageEvent, Request as WebRequest, RequestInit, Response as WebResponse, WebSocket, RequestInit, Response as WebResponse,
}; };
pub struct WebNavigatorBackend { pub struct WebNavigatorBackend {
@ -368,6 +369,7 @@ impl NavigatorBackend for WebNavigatorBackend {
fn connect_socket( fn connect_socket(
&mut self, &mut self,
host: String, host: String,
port: u16, port: u16,
// NOTE: WebSocket does not allow specifying a timeout, so this goes unused. // NOTE: WebSocket does not allow specifying a timeout, so this goes unused.
_timeout: Duration, _timeout: Duration,
@ -389,7 +391,7 @@ impl NavigatorBackend for WebNavigatorBackend {
tracing::info!("Connecting to {}", proxy.proxy_url); tracing::info!("Connecting to {}", proxy.proxy_url);
let ws = match WebSocket::new(&proxy.proxy_url) { let ws = match WebSocket::open(&proxy.proxy_url) {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {
tracing::error!("Failed to create WebSocket, reason {:?}", e); tracing::error!("Failed to create WebSocket, reason {:?}", e);
@ -400,70 +402,43 @@ impl NavigatorBackend for WebNavigatorBackend {
} }
}; };
ws.set_binary_type(web_sys::BinaryType::Arraybuffer); let (mut sink, mut stream) = ws.split();
sender
.send(SocketAction::Connect(handle, ConnectionState::Connected))
.expect("working channel send");
let sender_onopen = sender.clone(); // Spawn future to handle incoming messages.
let onopen_callback = Closure::<dyn FnMut()>::new(move || { let stream_sender = sender.clone();
sender_onopen self.spawn_future(Box::pin(async move {
.send(SocketAction::Connect(handle, ConnectionState::Connected)) while let Some(msg) = stream.next().await {
.expect("Working channel send"); match msg {
}); Ok(Message::Bytes(buf)) => stream_sender
.send(SocketAction::Data(handle, buf))
let sender_onmessage = sender.clone(); .expect("working channel send"),
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| { Ok(_) => tracing::warn!("Server sent unexpected text message"),
if let Ok(buf) = e.data().dyn_into::<js_sys::ArrayBuffer>() { Err(_) => {
// Handle array buffer. stream_sender
let array = js_sys::Uint8Array::new(&buf); .send(SocketAction::Close(handle))
sender_onmessage .expect("working channel send");
.send(SocketAction::Data(handle, array.to_vec())) return Ok(());
.expect("Working channel send"); }
} else if let Ok(_) = e.data().dyn_into::<web_sys::Blob>() { }
todo!("Unsupported blob payload");
} else {
todo!("Unsupported WebSocket payload");
} }
});
let sender_onclose = sender.clone(); Ok(())
let onclose_callback = Closure::<dyn FnMut(_)>::new(move |_e: CloseEvent| { }));
sender_onclose
.send(SocketAction::Close(handle))
.expect("working channel send");
});
let sender_onerror = sender.clone();
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |_e: ErrorEvent| {
sender_onerror
.send(SocketAction::Close(handle))
.expect("working channel send");
});
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
onmessage_callback.forget();
onclose_callback.forget();
onerror_callback.forget();
// Spawn future to handle outgoing messages. // Spawn future to handle outgoing messages.
self.spawn_future(Box::pin(async move { self.spawn_future(Box::pin(async move {
while let Ok(msg) = receiver.recv().await { while let Ok(msg) = receiver.recv().await {
if let Err(e) = ws.send_with_u8_array(&msg) { if let Err(e) = sink.send(Message::Bytes(msg)).await {
tracing::error!("Failed to send message to WebSocket {:?}", e); tracing::warn!("Failed to send message to WebSocket {}", e);
sender sender
.send(SocketAction::Close(handle)) .send(SocketAction::Close(handle))
.expect("working channel send"); .expect("working channel send");
} }
} }
// Close WebSocket as the sender was dropped.
if let Err(e) = ws.close() {
tracing::error!("Failed to close WebSocket connection {:?}", e);
}
Ok(()) Ok(())
})); }));
} }