diff --git a/.gitignore b/.gitignore index c849890..56fa846 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target dist/ +server_hash.txt diff --git a/Cargo.lock b/Cargo.lock index c1596fc..2f0c2ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index f8f53a6..a70004a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/app.rs b/src/app.rs index b29e3d5..6c0da58 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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, + }, + 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, } @@ -37,6 +55,13 @@ pub struct ServerState { pub channels: HashMap, pub users: HashMap, pub chat: Vec, + pub session: Option, +} + +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 = 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 {} ) } diff --git a/src/lib.rs b/src/lib.rs index 9a9eb9a..a01a8f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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>, + packets: UnboundedSender>, ) -> Result { let stream = window() .unwrap() @@ -219,14 +221,15 @@ async fn create_encoder_worklet( download_buffer.borrow_mut().clear(); } - let _ = packets.try_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio { - _dst: std::marker::PhantomData, - target: 0, - session_id: (), - seq_num: sequence_num, - payload: VoicePacketPayload::Opus(array.into(), false), - position_info: None, - }))); + let _ = + packets.unbounded_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio { + _dst: std::marker::PhantomData, + target: 0, + session_id: (), + seq_num: sequence_num, + payload: VoicePacketPayload::Opus(array.into(), false), + position_info: None, + }))); sequence_num = sequence_num.wrapping_add(2); }); @@ -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 { 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); // 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); 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) { @@ -370,180 +353,195 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver) { panic!("Did not receive connect command") }; - *STATE.server.write() = Default::default(); - *STATE.status.write() = ConnectionState::Connecting; - - console::log_1(&"Rust via WASM!".into()); - - let Ok(server_hash): Result, _> = env!("WEBTRANSPORT_SERVER_HASH") - .trim_matches(&['[', ']']) - .split(',') - .map(|x| x.trim().parse()) - .collect() - else { - panic!("could not parse server hash") - }; - let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice()); - - let object = web_sys::js_sys::Object::new(); - - Reflect::set( - &object, - &JsValue::from_str("algorithm"), - &JsValue::from_str("sha-256"), - ) - .unwrap(); - - web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).unwrap(); - - let array = web_sys::js_sys::Array::new(); - array.push(&object); - - console::log_1(&object.clone().into()); - console::log_1(&"Created option object!".into()); - - let mut options = WebTransportOptions::new(); - options.server_certificate_hashes(&array); - - 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!(); - } - }; - - 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 { - console::log_1(&e.into()); - panic!(); + if let Err(error) = network_connect(address, username, &mut event_rx).await { + console::error_1(&error); } + } +} - console::log_1(&"Transport is ready.".into()); +macro_rules! bail { + ($($x:tt)*) => { + return Err(wasm_bindgen::JsError::new(&format!($($x)*)).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!(); - } - }; +async fn network_connect( + address: String, + username: String, + event_rx: &mut UnboundedReceiver, +) -> Result<(), JsValue> { + *STATE.server.write() = Default::default(); + *STATE.status.write() = ConnectionState::Connecting; - let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into()); - let wasm_stream_writable = wasm_streams::WritableStream::from_raw(stream.writable().into()); + console::log_1(&"Rust via WASM!".into()); - let read_codec = ClientControlCodec::new(); - let write_codec = ClientControlCodec::new(); + let Ok(server_hash): Result, _> = include_str!("../server_hash.txt") + .trim() + .trim_matches(&['[', ']']) + .split(',') + .map(|x| x.trim().parse()) + .collect() + else { + bail!("could not parse server hash"); + }; + let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice()); - 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 object = web_sys::js_sys::Object::new(); - let (send_chan, writer_recv_chan) = async_std::channel::unbounded(); + Reflect::set( + &object, + &JsValue::from_str("algorithm"), + &JsValue::from_str("sha-256"), + )?; + web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash)?; + + let array = web_sys::js_sys::Array::new(); + array.push(&object); + + console::log_1(&object.clone().into()); + console::log_1(&"Created option object!".into()); + + let mut options = WebTransportOptions::new(); + options.server_certificate_hashes(&array); + + console::log_1(&"Created WebTransportOptions!".into()); + + 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 { + console::log_1(&e.into()); + panic!(); + } + + console::log_1(&"Transport is ready.".into()); + + 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()); + + let read_codec = ClientControlCodec::new(); + let write_codec = ClientControlCodec::new(); + + 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 send_chan, mut writer_recv_chan) = futures_channel::mpsc::unbounded(); + + spawn(async move { + while let Some(msg) = writer_recv_chan.next().await { + if let Err(e) = writer.send(msg).await { + console::error_1(&e.to_string().into()); + break; + } + } + }); + + // Get version packet + 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()); + + // Send version packet + let mut msg = msgs::Version::new(); + msg.set_version(0x000010204); + msg.set_release(format!("{} {}", "mumbleweb2", "6.9.0")); + //msg.set_os("Chrome".to_string()); + send_chan.send(msg.into()).await.unwrap(); + console::log_1(&"Sent version packet".into()); + + // Send authenticate packet + let mut msg = msgs::Authenticate::new(); + msg.set_username(username); + msg.set_opus(true); + send_chan.send(msg.into()).await.unwrap(); + console::log_1(&"Sent authenticate packet".into()); + + // Spawn worker to send pings + { + let mut send_chan = send_chan.clone(); spawn(async move { - while let Ok(msg) = writer_recv_chan.recv().await { - if let Err(e) = writer.send(msg).await { + loop { + console::log_1(&"Sending ping".into()); + if let Err(e) = send_chan.send(msgs::Ping::new().into()).await { + console::log_1(&"could not ping".into()); console::log_1(&e.to_string().into()); break; } + + TimeoutFuture::new(3000).await; } }); - - // Get version packet - let version = reader.next().await.unwrap().unwrap(); - console::log_1(&"Got version packet".into()); - console::log_1(&format!("{:#?}", version).into()); - - // Send version packet - let mut msg = msgs::Version::new(); - msg.set_version(0x000010204); - msg.set_release(format!("{} {}", "mumbleweb2", "6.9.0")); - //msg.set_os("Chrome".to_string()); - send_chan.send(msg.into()).await.unwrap(); - console::log_1(&"Sent version packet".into()); - - // Send authenticate packet - let mut msg = msgs::Authenticate::new(); - msg.set_username(username); - msg.set_opus(true); - send_chan.send(msg.into()).await.unwrap(); - console::log_1(&"Sent authenticate packet".into()); - - // Spawn worker to send pings - { - let send_chan = send_chan.clone(); - spawn(async move { - loop { - console::log_1(&"Sending ping".into()); - if let Err(e) = send_chan.send(msgs::Ping::new().into()).await { - console::log_1(&"could not ping".into()); - console::log_1(&e.to_string().into()); - break; - } - - async_std::task::sleep(Duration::from_millis(3000)).await; - } - }); - } - - // Create MediaStreams to playback decoded audio - // The audio context is used to reproduce audio. - let audio_context = configure_audio_context(); - - let audio_context_worklet = audio_context.clone(); - let packet_sender_worklet = send_chan.clone(); - spawn(async move { - match create_encoder_worklet(&audio_context_worklet, packet_sender_worklet).await { - Ok(node) => console::log_2(&"Created audio worklet:".into(), &node), - Err(err) => console::error_1(&err), - } - }); - - *STATE.status.write() = ConnectionState::Connected; - - // Create map of session_id -> AudioDecoder - let mut decoder_map = HashMap::new(); - - loop { - select! { - packet = reader.next().fuse() => { - match packet { - Some(Ok(msg)) => accept_packet(msg, &audio_context, &mut decoder_map), - Some(Err(err)) => panic!("{err}"), - None => break, - } - } - command = event_rx.next() => { - match command { - Some(Command::Disconnect) => break, - _ => continue, - } - } - } - } - send_chan.close(); - *STATE.status.write() = ConnectionState::Disconnected; } + + // Create MediaStreams to playback decoded audio + // The audio context is used to reproduce audio. + let audio_context = configure_audio_context(); + + let audio_context_worklet = audio_context.clone(); + let packet_sender_worklet = send_chan.clone(); + spawn(async move { + match create_encoder_worklet(&audio_context_worklet, packet_sender_worklet).await { + Ok(node) => console::log_2(&"Created audio worklet:".into(), &node), + Err(err) => console::error_1(&err), + } + }); + + // Create map of session_id -> AudioDecoder + let mut decoder_map = HashMap::new(); + + loop { + select! { + packet = reader.next().fuse() => { + match packet { + 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, + } + } + } + } + send_chan.close(); + *STATE.status.write() = ConnectionState::Disconnected; + Ok(()) } fn accept_packet( msg: ControlPacket, audio_context: &AudioContext, decoder_map: &mut HashMap, -) { - 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(()) }