some error handling improvements & start on chat send

This commit is contained in:
2024-09-28 14:42:29 -06:00
parent de17960335
commit 8f420e6efa
5 changed files with 355 additions and 498 deletions
+1
View File
@@ -1,2 +1,3 @@
/target
dist/
server_hash.txt
Generated
+78 -285
View File
@@ -29,6 +29,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
@@ -47,17 +56,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-channel"
version = "2.3.1"
@@ -70,119 +68,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand 2.1.0",
"futures-lite 2.3.0",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
"async-channel 2.3.1",
"async-executor",
"async-io 2.3.3",
"async-lock 3.4.0",
"blocking",
"futures-lite 2.3.0",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock 2.8.0",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-lite 1.13.0",
"log",
"parking",
"polling 2.8.0",
"rustix 0.37.27",
"slab",
"socket2 0.4.10",
"waker-fn",
]
[[package]]
name = "async-io"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964"
dependencies = [
"async-lock 3.4.0",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.3.0",
"parking",
"polling 3.7.2",
"rustix 0.38.34",
"slab",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.3.1",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-channel 1.9.0",
"async-global-executor",
"async-io 1.13.0",
"async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.7.1"
@@ -276,10 +161,10 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [
"async-channel 2.3.1",
"async-channel",
"async-task",
"futures-io",
"futures-lite 2.3.0",
"futures-lite",
"piper",
]
@@ -935,12 +820,6 @@ dependencies = [
"serde",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.3.1"
@@ -958,19 +837,10 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener 5.3.1",
"event-listener",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fastrand"
version = "2.1.0"
@@ -1061,31 +931,13 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
dependencies = [
"fastrand 2.1.0",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
@@ -1179,7 +1031,7 @@ dependencies = [
"gloo-net 0.3.1",
"gloo-render",
"gloo-storage",
"gloo-timers",
"gloo-timers 0.2.6",
"gloo-utils 0.1.7",
"gloo-worker",
]
@@ -1317,6 +1169,16 @@ name = "gloo-timers"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "gloo-timers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
"futures-channel",
"futures-core",
@@ -1415,18 +1277,6 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "home"
version = "0.5.9"
@@ -1507,7 +1357,7 @@ dependencies = [
"httpdate",
"itoa 1.0.11",
"pin-project-lite",
"socket2 0.5.7",
"socket2",
"tokio",
"tower-service",
"tracing",
@@ -1562,15 +1412,6 @@ dependencies = [
"cfb",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "internment"
version = "0.7.5"
@@ -1607,17 +1448,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "ipnet"
version = "2.9.0"
@@ -1669,15 +1499,6 @@ dependencies = [
"semver",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1696,12 +1517,6 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@@ -1723,9 +1538,6 @@ name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [
"value-bag",
]
[[package]]
name = "lol_html"
@@ -1801,6 +1613,17 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "markdown"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef3aab6a1d529b112695f72beec5ee80e729cb45af58663ec902c8fac764ecdd"
dependencies = [
"lazy_static",
"pipeline",
"regex",
]
[[package]]
name = "matches"
version = "0.1.10"
@@ -1873,14 +1696,16 @@ name = "mumble-webtransport"
version = "0.1.0"
dependencies = [
"anyhow",
"async-std",
"asynchronous-codec",
"byteorder",
"dioxus",
"dioxus-web",
"futures",
"futures-channel",
"gloo-timers 0.3.0",
"html-purifier",
"manganis",
"markdown",
"merge-io",
"mumble-protocol-2x",
"ogg",
@@ -2160,6 +1985,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pipeline"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0"
[[package]]
name = "piper"
version = "0.2.3"
@@ -2167,7 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
dependencies = [
"atomic-waker",
"fastrand 2.1.0",
"fastrand",
"futures-io",
]
@@ -2177,37 +2008,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]]
name = "polling"
version = "3.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi 0.4.0",
"pin-project-lite",
"rustix 0.38.34",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@@ -2342,6 +2142,35 @@ dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "reqwest"
version = "0.11.27"
@@ -2403,20 +2232,6 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "0.38.34"
@@ -2426,7 +2241,7 @@ dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys 0.4.14",
"linux-raw-sys",
"windows-sys 0.52.0",
]
@@ -2760,16 +2575,6 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "socket2"
version = "0.5.7"
@@ -2851,8 +2656,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand 2.1.0",
"rustix 0.38.34",
"fastrand",
"rustix",
"windows-sys 0.52.0",
]
@@ -2924,7 +2729,7 @@ dependencies = [
"libc",
"mio",
"pin-project-lite",
"socket2 0.5.7",
"socket2",
"windows-sys 0.48.0",
]
@@ -3117,12 +2922,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -3135,12 +2934,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
[[package]]
name = "want"
version = "0.3.1"
+3 -1
View File
@@ -19,9 +19,11 @@ wasm-bindgen = "0.2.92"
wasm-bindgen-futures = "0.4.42"
wasm-streams = "0.4.0"
web-sys = { version = "0.3.70", features = ["WebTransport", "console", "WebTransportOptions", "WebTransportBidirectionalStream", "WebTransportSendStream", "WebTransportReceiveStream", "Navigator", "MediaDevices", "AudioDecoder", "AudioDecoderInit", "AudioData", "AudioEncoderConfig", "AudioDecoderConfig", "EncodedAudioChunk", "EncodedAudioChunkInit", "EncodedAudioChunkType", "CodecState", "MediaStreamTrackGenerator", "MediaStreamTrackGeneratorInit", "AudioContext", "AudioContextOptions", "MediaStream", "GainNode", "MediaStreamAudioSourceNode", "BaseAudioContext", "AudioDestinationNode", "AudioWorkletNode", "AudioWorklet", "AudioWorkletProcessor", "MediaStreamConstraints", "WorkletOptions", "AudioEncoder", "AudioEncoderInit", "AudioDataInit", "HtmlAnchorElement", "Url", "Blob", "AudioDataCopyToOptions", "AudioSampleFormat"] }
async-std = "1.12.0"
anyhow = "1.0.86"
byteorder = "1.5.0"
ogg = "0.9.1"
ordermap = "0.5.3"
html-purifier = "0.3.0"
markdown = "0.3.0"
gloo-timers = { version = "0.3.0", features = ["futures"] }
futures-channel = "0.3.30"
+50 -7
View File
@@ -5,13 +5,31 @@ use std::collections::{BTreeMap, BTreeSet, HashMap};
use dioxus::prelude::*;
use ordermap::OrderSet;
use super::Command;
use super::Command::*;
use super::ConnectionState::{self, *};
pub type ChannelId = u32;
pub type UserId = u32;
pub enum ConnectionState {
Disconnected,
Connecting,
Connected,
}
pub enum Command {
Connect {
address: String,
username: String,
hash: String,
},
SendChat {
markdown: String,
channels: Vec<ChannelId>,
},
Disconnect,
}
use Command::*;
use ConnectionState::*;
#[derive(Default)]
pub struct ChannelState {
pub name: String,
@@ -27,7 +45,7 @@ pub struct UserState {
}
pub struct Chat {
pub text: String,
pub raw: String,
pub dangerous_html: String,
pub sender: Option<UserId>,
}
@@ -37,6 +55,13 @@ pub struct ServerState {
pub channels: HashMap<ChannelId, ChannelState>,
pub users: HashMap<UserId, UserState>,
pub chat: Vec<Chat>,
pub session: Option<UserId>,
}
impl ServerState {
pub fn this_user(&self) -> Option<&UserState> {
self.users.get(&self.session?)
}
}
pub struct State {
@@ -80,8 +105,10 @@ pub fn Channel(id: ChannelId) -> Element {
}
#[component]
pub fn ChatHistory() -> Element {
pub fn ChatView() -> Element {
let server = STATE.server.read();
let mut draft = use_signal(|| "".to_string());
//let net: Coroutine<Command> = use_coroutine_handle();
rsx!(
div {
style: "margin: 16px; padding: 16px; border: solid black 1px;",
@@ -98,6 +125,22 @@ pub fn ChatHistory() -> Element {
}
hr {}
}
input {
placeholder: "say something",
value: "{draft.read()}",
oninput: move |evt| draft.set(evt.value().clone()),
}
button {
onclick: move |_| {
/*if let Some(user) = server.this_user() {
net.send(SendChat {
markdown: draft.write().split_off(0),
channels: vec![user.channel],
});
}*/
},
"Send"
}
}
)
}
@@ -114,7 +157,7 @@ pub fn ServerView() -> Element {
}
}
}
ChatHistory {}
ChatView {}
)
}
+104 -86
View File
@@ -1,25 +1,27 @@
pub mod app;
use app::ChannelState;
use app::ChannelId;
use app::Chat;
use app::UserState;
use app::Command;
use app::ConnectionState;
use dioxus::prelude::*;
use anyhow::Error;
use app::STATE;
use async_std::channel::Sender;
use futures::select;
use futures::FutureExt;
use futures::SinkExt;
use futures::StreamExt;
use futures_channel::mpsc::UnboundedSender;
use gloo_timers::future::TimeoutFuture;
use manganis::{file, mg};
use markdown;
use mumble_protocol::control::ControlPacket;
use mumble_protocol::control::{msgs, ClientControlCodec};
use mumble_protocol::voice::VoicePacket;
use mumble_protocol::voice::VoicePacketDst;
use mumble_protocol::voice::VoicePacketPayload;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::time::Duration;
use std::fmt;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local as spawn;
use wasm_bindgen_futures::{future_to_promise, JsFuture};
@@ -47,11 +49,11 @@ use web_sys::MediaStreamTrackGenerator;
use web_sys::MediaStreamTrackGeneratorInit;
use web_sys::MessageEvent;
use web_sys::WebTransport;
use web_sys::WebTransportBidirectionalStream;
use web_sys::WebTransportOptions;
use web_sys::WorkletOptions;
mod ass {
use byteorder::{ByteOrder, LittleEndian};
use ogg::PacketWriter;
@@ -168,7 +170,7 @@ impl PromiseExt for Promise {
async fn create_encoder_worklet(
audio_context: &AudioContext,
packets: Sender<ControlPacket<mumble_protocol::Serverbound>>,
packets: UnboundedSender<ControlPacket<mumble_protocol::Serverbound>>,
) -> Result<AudioWorkletNode, JsValue> {
let stream = window()
.unwrap()
@@ -219,7 +221,8 @@ async fn create_encoder_worklet(
download_buffer.borrow_mut().clear();
}
let _ = packets.try_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio {
let _ =
packets.unbounded_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio {
_dst: std::marker::PhantomData,
target: 0,
session_id: (),
@@ -284,27 +287,23 @@ async fn create_encoder_worklet(
Ok(worklet_node)
}
fn create_decoder(audio_context: &AudioContext) -> AudioDecoder {
fn create_decoder(audio_context: &AudioContext) -> Result<AudioDecoder, JsValue> {
let audio_stream_generator =
MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio")).unwrap();
MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio"))?;
// Create MediaStream from MediaStreamTrackGenerator
let js_tracks = web_sys::js_sys::Array::new();
js_tracks.push(&audio_stream_generator);
let media_stream = MediaStream::new_with_tracks(&js_tracks).unwrap();
let media_stream = MediaStream::new_with_tracks(&js_tracks)?;
// Create MediaStreamAudioSourceNode
let audio_source = audio_context
.create_media_stream_source(&media_stream)
.unwrap();
let audio_source = audio_context.create_media_stream_source(&media_stream)?;
// Connect output of audio_source to audio_context (browser audio)
audio_source
.connect_with_audio_node(&audio_context.destination())
.unwrap();
audio_source.connect_with_audio_node(&audio_context.destination())?;
// Create callback functions for AudioDecoder
let error = Closure::wrap(Box::new(move |e: JsValue| {
console::log_1(&e);
console::error_1(&e);
}) as Box<dyn FnMut(JsValue)>);
// This knows what MediaStreamTrackGenerator to use as it closes around it
@@ -316,23 +315,22 @@ fn create_decoder(audio_context: &AudioContext) -> AudioDecoder {
if let Err(e) = writable.get_writer().map(|writer| {
spawn(async move {
if let Err(e) = JsFuture::from(writer.ready()).await {
console::log_1(&format!("write chunk error {:?}", e).into());
console::error_1(&format!("write chunk ready error {:?}", e).into());
}
if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data)).await {
console::log_1(&format!("write chunk error {:?}", e).into());
console::error_1(&format!("write chunk error {:?}", e).into());
};
writer.release_lock();
});
}) {
console::log_1(&e);
console::error_1(&e);
}
}) as Box<dyn FnMut(AudioData)>);
let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new(
error.as_ref().unchecked_ref(),
output.as_ref().unchecked_ref(),
))
.unwrap();
))?;
audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000));
console::log_1(&"Created Audio Decoder".into());
@@ -341,22 +339,7 @@ fn create_decoder(audio_context: &AudioContext) -> AudioDecoder {
error.forget();
output.forget();
audio_decoder
}
pub enum ConnectionState {
Disconnected,
Connecting,
Connected,
}
pub enum Command {
Connect {
address: String,
username: String,
hash: String,
},
Disconnect,
Ok(audio_decoder)
}
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
@@ -370,18 +353,36 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
panic!("Did not receive connect command")
};
if let Err(error) = network_connect(address, username, &mut event_rx).await {
console::error_1(&error);
}
}
}
macro_rules! bail {
($($x:tt)*) => {
return Err(wasm_bindgen::JsError::new(&format!($($x)*)).into());
};
}
async fn network_connect(
address: String,
username: String,
event_rx: &mut UnboundedReceiver<Command>,
) -> Result<(), JsValue> {
*STATE.server.write() = Default::default();
*STATE.status.write() = ConnectionState::Connecting;
console::log_1(&"Rust via WASM!".into());
let Ok(server_hash): Result<Vec<u8>, _> = env!("WEBTRANSPORT_SERVER_HASH")
let Ok(server_hash): Result<Vec<u8>, _> = include_str!("../server_hash.txt")
.trim()
.trim_matches(&['[', ']'])
.split(',')
.map(|x| x.trim().parse())
.collect()
else {
panic!("could not parse server hash")
bail!("could not parse server hash");
};
let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice());
@@ -391,10 +392,9 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
&object,
&JsValue::from_str("algorithm"),
&JsValue::from_str("sha-256"),
)
.unwrap();
)?;
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).unwrap();
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash)?;
let array = web_sys::js_sys::Array::new();
array.push(&object);
@@ -407,16 +407,8 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
console::log_1(&"Created WebTransportOptions!".into());
let transport = match WebTransport::new_with_options(&address, &options) {
Ok(x) => x,
Err(e) => {
console::log_1(&e.into());
panic!();
}
};
let transport = WebTransport::new_with_options(&address, &options)?;
console::log_1(&"Created WebTransport connection object.".into());
console::log_1(&transport.clone().into());
if let Err(e) = wasm_bindgen_futures::JsFuture::from(transport.ready()).await {
@@ -426,16 +418,10 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
console::log_1(&"Transport is ready.".into());
let stream: web_sys::WebTransportBidirectionalStream =
match wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream())
.await
{
Ok(x) => x.into(),
Err(e) => {
console::log_1(&e.into());
panic!();
}
};
let stream: WebTransportBidirectionalStream =
wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream())
.await?
.into();
let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into());
let wasm_stream_writable = wasm_streams::WritableStream::from_raw(stream.writable().into());
@@ -445,24 +431,26 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
let mut reader =
asynchronous_codec::FramedRead::new(wasm_stream_readable.into_async_read(), read_codec);
let mut writer = asynchronous_codec::FramedWrite::new(
wasm_stream_writable.into_async_write(),
write_codec,
);
let mut writer =
asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec);
let (send_chan, writer_recv_chan) = async_std::channel::unbounded();
let (mut send_chan, mut writer_recv_chan) = futures_channel::mpsc::unbounded();
spawn(async move {
while let Ok(msg) = writer_recv_chan.recv().await {
while let Some(msg) = writer_recv_chan.next().await {
if let Err(e) = writer.send(msg).await {
console::log_1(&e.to_string().into());
console::error_1(&e.to_string().into());
break;
}
}
});
// Get version packet
let version = reader.next().await.unwrap().unwrap();
let version = match reader.next().await {
Some(Ok(v)) => v,
Some(Err(err)) => bail!("bad version packet: {err:?}"),
None => bail!("no version was recieved"),
};
console::log_1(&"Got version packet".into());
console::log_1(&format!("{:#?}", version).into());
@@ -483,7 +471,7 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
// Spawn worker to send pings
{
let send_chan = send_chan.clone();
let mut send_chan = send_chan.clone();
spawn(async move {
loop {
console::log_1(&"Sending ping".into());
@@ -493,7 +481,7 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
break;
}
async_std::task::sleep(Duration::from_millis(3000)).await;
TimeoutFuture::new(3000).await;
}
});
}
@@ -511,8 +499,6 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
}
});
*STATE.status.write() = ConnectionState::Connected;
// Create map of session_id -> AudioDecoder
let mut decoder_map = HashMap::new();
@@ -520,14 +506,26 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
select! {
packet = reader.next().fuse() => {
match packet {
Some(Ok(msg)) => accept_packet(msg, &audio_context, &mut decoder_map),
Some(Err(err)) => panic!("{err}"),
Some(Ok(msg)) => {
let res =accept_packet(msg, &audio_context, &mut decoder_map);
if let Err(err) = res {
console::error_1(&err.into());
}
},
Some(Err(err)) => console::error_1(&err.to_string().into()),
None => break,
}
}
command = event_rx.next() => {
match command {
Some(Command::Disconnect) => break,
Some(Command::SendChat { markdown, channels }) => {
let html_text = markdown::to_html(&markdown);
let mut u = msgs::TextMessage::new();
u.set_message(html_text);
u.set_channel_id(channels);
let _ = send_chan.send(u.into());
},
_ => continue,
}
}
@@ -535,15 +533,15 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
}
send_chan.close();
*STATE.status.write() = ConnectionState::Disconnected;
}
Ok(())
}
fn accept_packet(
msg: ControlPacket<mumble_protocol::Clientbound>,
audio_context: &AudioContext,
decoder_map: &mut HashMap<u32, AudioDecoder>,
) {
if !matches!(msg, ControlPacket::UDPTunnel(_)) {
) -> Result<(), JsValue> {
if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) {
console::log_1(&format!("{:#?}", msg).into());
}
match msg {
@@ -558,9 +556,12 @@ fn accept_packet(
position_info,
} => {
// Get or create audio decoder for this user
let audio_decoder = decoder_map
.entry(session_id)
.or_insert_with(|| create_decoder(&audio_context));
let audio_decoder = match decoder_map.entry(session_id) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(create_decoder(&audio_context)?)
}
};
// This will over time (as users join and leave) leak
// AudioDecoders, MediaStreamTrackGenerators, MediaStreams, and MediaStreamAudioSourceNodes.
// A better way to handle this would be to delete and create all the audio
@@ -568,7 +569,7 @@ fn accept_packet(
// any audio packets that come in the meantime.
if let VoicePacketPayload::Opus(audio_payload, end_bit) = payload {
let js_audio_payload = Uint8Array::from(audio_payload.as_ref());
audio_decoder.decode(
let _ = audio_decoder.decode(
&EncodedAudioChunk::new(&EncodedAudioChunkInit::new(
&js_audio_payload.into(),
0.0,
@@ -668,10 +669,27 @@ fn accept_packet(
None
},
dangerous_html: html_purifier::purifier(&text, Default::default()),
text: text,
raw: text,
});
}
}
ControlPacket::ServerSync(u) => {
*STATE.status.write() = ConnectionState::Connected;
let mut server = STATE.server.write();
if u.has_welcome_text() {
let text = u.get_welcome_text().to_string();
server.chat.push(Chat {
sender: None,
dangerous_html: html_purifier::purifier(&text, Default::default()),
raw: text,
});
}
if u.has_session() {
server.session = Some(u.get_session());
}
}
_ => {}
}
Ok(())
}