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 /target
dist/ dist/
server_hash.txt
Generated
+78 -285
View File
@@ -29,6 +29,15 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.18" version = "0.2.18"
@@ -47,17 +56,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" 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]] [[package]]
name = "async-channel" name = "async-channel"
version = "2.3.1" version = "2.3.1"
@@ -70,119 +68,6 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "async-task" name = "async-task"
version = "4.7.1" version = "4.7.1"
@@ -276,10 +161,10 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [ dependencies = [
"async-channel 2.3.1", "async-channel",
"async-task", "async-task",
"futures-io", "futures-io",
"futures-lite 2.3.0", "futures-lite",
"piper", "piper",
] ]
@@ -935,12 +820,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.3.1" version = "5.3.1"
@@ -958,19 +837,10 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [ dependencies = [
"event-listener 5.3.1", "event-listener",
"pin-project-lite", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.1.0" version = "2.1.0"
@@ -1061,31 +931,13 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 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]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.3.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
dependencies = [ dependencies = [
"fastrand 2.1.0",
"futures-core", "futures-core",
"futures-io",
"parking",
"pin-project-lite", "pin-project-lite",
] ]
@@ -1179,7 +1031,7 @@ dependencies = [
"gloo-net 0.3.1", "gloo-net 0.3.1",
"gloo-render", "gloo-render",
"gloo-storage", "gloo-storage",
"gloo-timers", "gloo-timers 0.2.6",
"gloo-utils 0.1.7", "gloo-utils 0.1.7",
"gloo-worker", "gloo-worker",
] ]
@@ -1317,6 +1169,16 @@ name = "gloo-timers"
version = "0.2.6" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" 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 = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -1415,18 +1277,6 @@ dependencies = [
"allocator-api2", "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]] [[package]]
name = "home" name = "home"
version = "0.5.9" version = "0.5.9"
@@ -1507,7 +1357,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa 1.0.11", "itoa 1.0.11",
"pin-project-lite", "pin-project-lite",
"socket2 0.5.7", "socket2",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -1562,15 +1412,6 @@ dependencies = [
"cfb", "cfb",
] ]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "internment" name = "internment"
version = "0.7.5" version = "0.7.5"
@@ -1607,17 +1448,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" 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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@@ -1669,15 +1499,6 @@ dependencies = [
"semver", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -1696,12 +1517,6 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@@ -1723,9 +1538,6 @@ name = "log"
version = "0.4.22" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "lol_html" name = "lol_html"
@@ -1801,6 +1613,17 @@ dependencies = [
"tracing-subscriber", "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]] [[package]]
name = "matches" name = "matches"
version = "0.1.10" version = "0.1.10"
@@ -1873,14 +1696,16 @@ name = "mumble-webtransport"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std",
"asynchronous-codec", "asynchronous-codec",
"byteorder", "byteorder",
"dioxus", "dioxus",
"dioxus-web", "dioxus-web",
"futures", "futures",
"futures-channel",
"gloo-timers 0.3.0",
"html-purifier", "html-purifier",
"manganis", "manganis",
"markdown",
"merge-io", "merge-io",
"mumble-protocol-2x", "mumble-protocol-2x",
"ogg", "ogg",
@@ -2160,6 +1985,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pipeline"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0"
[[package]] [[package]]
name = "piper" name = "piper"
version = "0.2.3" version = "0.2.3"
@@ -2167,7 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"fastrand 2.1.0", "fastrand",
"futures-io", "futures-io",
] ]
@@ -2177,37 +2008,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 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]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@@ -2342,6 +2142,35 @@ dependencies = [
"bitflags 2.6.0", "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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.27" version = "0.11.27"
@@ -2403,20 +2232,6 @@ dependencies = [
"semver", "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]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.34" version = "0.38.34"
@@ -2426,7 +2241,7 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.14", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -2760,16 +2575,6 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.7" version = "0.5.7"
@@ -2851,8 +2656,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand 2.1.0", "fastrand",
"rustix 0.38.34", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -2924,7 +2729,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"socket2 0.5.7", "socket2",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@@ -3117,12 +2922,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -3135,12 +2934,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
+3 -1
View File
@@ -19,9 +19,11 @@ wasm-bindgen = "0.2.92"
wasm-bindgen-futures = "0.4.42" wasm-bindgen-futures = "0.4.42"
wasm-streams = "0.4.0" 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"] } 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" anyhow = "1.0.86"
byteorder = "1.5.0" byteorder = "1.5.0"
ogg = "0.9.1" ogg = "0.9.1"
ordermap = "0.5.3" ordermap = "0.5.3"
html-purifier = "0.3.0" 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 dioxus::prelude::*;
use ordermap::OrderSet; use ordermap::OrderSet;
use super::Command;
use super::Command::*;
use super::ConnectionState::{self, *};
pub type ChannelId = u32; pub type ChannelId = u32;
pub type UserId = 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)] #[derive(Default)]
pub struct ChannelState { pub struct ChannelState {
pub name: String, pub name: String,
@@ -27,7 +45,7 @@ pub struct UserState {
} }
pub struct Chat { pub struct Chat {
pub text: String, pub raw: String,
pub dangerous_html: String, pub dangerous_html: String,
pub sender: Option<UserId>, pub sender: Option<UserId>,
} }
@@ -37,6 +55,13 @@ pub struct ServerState {
pub channels: HashMap<ChannelId, ChannelState>, pub channels: HashMap<ChannelId, ChannelState>,
pub users: HashMap<UserId, UserState>, pub users: HashMap<UserId, UserState>,
pub chat: Vec<Chat>, 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 { pub struct State {
@@ -80,8 +105,10 @@ pub fn Channel(id: ChannelId) -> Element {
} }
#[component] #[component]
pub fn ChatHistory() -> Element { pub fn ChatView() -> Element {
let server = STATE.server.read(); let server = STATE.server.read();
let mut draft = use_signal(|| "".to_string());
//let net: Coroutine<Command> = use_coroutine_handle();
rsx!( rsx!(
div { div {
style: "margin: 16px; padding: 16px; border: solid black 1px;", style: "margin: 16px; padding: 16px; border: solid black 1px;",
@@ -98,6 +125,22 @@ pub fn ChatHistory() -> Element {
} }
hr {} 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 {}
) )
} }
+223 -205
View File
@@ -1,25 +1,27 @@
pub mod app; pub mod app;
use app::ChannelState; use app::ChannelId;
use app::Chat; use app::Chat;
use app::UserState; use app::Command;
use app::ConnectionState;
use dioxus::prelude::*; use dioxus::prelude::*;
use anyhow::Error;
use app::STATE; use app::STATE;
use async_std::channel::Sender;
use futures::select; use futures::select;
use futures::FutureExt; use futures::FutureExt;
use futures::SinkExt; use futures::SinkExt;
use futures::StreamExt; use futures::StreamExt;
use futures_channel::mpsc::UnboundedSender;
use gloo_timers::future::TimeoutFuture;
use manganis::{file, mg}; use manganis::{file, mg};
use markdown;
use mumble_protocol::control::ControlPacket; use mumble_protocol::control::ControlPacket;
use mumble_protocol::control::{msgs, ClientControlCodec}; use mumble_protocol::control::{msgs, ClientControlCodec};
use mumble_protocol::voice::VoicePacket; use mumble_protocol::voice::VoicePacket;
use mumble_protocol::voice::VoicePacketDst;
use mumble_protocol::voice::VoicePacketPayload; use mumble_protocol::voice::VoicePacketPayload;
use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::fmt;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local as spawn; use wasm_bindgen_futures::spawn_local as spawn;
use wasm_bindgen_futures::{future_to_promise, JsFuture}; use wasm_bindgen_futures::{future_to_promise, JsFuture};
@@ -47,11 +49,11 @@ use web_sys::MediaStreamTrackGenerator;
use web_sys::MediaStreamTrackGeneratorInit; use web_sys::MediaStreamTrackGeneratorInit;
use web_sys::MessageEvent; use web_sys::MessageEvent;
use web_sys::WebTransport; use web_sys::WebTransport;
use web_sys::WebTransportBidirectionalStream;
use web_sys::WebTransportOptions; use web_sys::WebTransportOptions;
use web_sys::WorkletOptions; use web_sys::WorkletOptions;
mod ass { mod ass {
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use ogg::PacketWriter; use ogg::PacketWriter;
@@ -168,7 +170,7 @@ impl PromiseExt for Promise {
async fn create_encoder_worklet( async fn create_encoder_worklet(
audio_context: &AudioContext, audio_context: &AudioContext,
packets: Sender<ControlPacket<mumble_protocol::Serverbound>>, packets: UnboundedSender<ControlPacket<mumble_protocol::Serverbound>>,
) -> Result<AudioWorkletNode, JsValue> { ) -> Result<AudioWorkletNode, JsValue> {
let stream = window() let stream = window()
.unwrap() .unwrap()
@@ -219,14 +221,15 @@ async fn create_encoder_worklet(
download_buffer.borrow_mut().clear(); download_buffer.borrow_mut().clear();
} }
let _ = packets.try_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio { let _ =
_dst: std::marker::PhantomData, packets.unbounded_send(ControlPacket::UDPTunnel(Box::new(VoicePacket::Audio {
target: 0, _dst: std::marker::PhantomData,
session_id: (), target: 0,
seq_num: sequence_num, session_id: (),
payload: VoicePacketPayload::Opus(array.into(), false), seq_num: sequence_num,
position_info: None, payload: VoicePacketPayload::Opus(array.into(), false),
}))); position_info: None,
})));
sequence_num = sequence_num.wrapping_add(2); sequence_num = sequence_num.wrapping_add(2);
}); });
@@ -284,27 +287,23 @@ async fn create_encoder_worklet(
Ok(worklet_node) Ok(worklet_node)
} }
fn create_decoder(audio_context: &AudioContext) -> AudioDecoder { fn create_decoder(audio_context: &AudioContext) -> Result<AudioDecoder, JsValue> {
let audio_stream_generator = let audio_stream_generator =
MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio")).unwrap(); MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio"))?;
// Create MediaStream from MediaStreamTrackGenerator // Create MediaStream from MediaStreamTrackGenerator
let js_tracks = web_sys::js_sys::Array::new(); let js_tracks = web_sys::js_sys::Array::new();
js_tracks.push(&audio_stream_generator); 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 // Create MediaStreamAudioSourceNode
let audio_source = audio_context let audio_source = audio_context.create_media_stream_source(&media_stream)?;
.create_media_stream_source(&media_stream)
.unwrap();
// Connect output of audio_source to audio_context (browser audio) // Connect output of audio_source to audio_context (browser audio)
audio_source audio_source.connect_with_audio_node(&audio_context.destination())?;
.connect_with_audio_node(&audio_context.destination())
.unwrap();
// Create callback functions for AudioDecoder // Create callback functions for AudioDecoder
let error = Closure::wrap(Box::new(move |e: JsValue| { let error = Closure::wrap(Box::new(move |e: JsValue| {
console::log_1(&e); console::error_1(&e);
}) as Box<dyn FnMut(JsValue)>); }) as Box<dyn FnMut(JsValue)>);
// This knows what MediaStreamTrackGenerator to use as it closes around it // 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| { if let Err(e) = writable.get_writer().map(|writer| {
spawn(async move { spawn(async move {
if let Err(e) = JsFuture::from(writer.ready()).await { 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 { 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(); writer.release_lock();
}); });
}) { }) {
console::log_1(&e); console::error_1(&e);
} }
}) as Box<dyn FnMut(AudioData)>); }) as Box<dyn FnMut(AudioData)>);
let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new( let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new(
error.as_ref().unchecked_ref(), error.as_ref().unchecked_ref(),
output.as_ref().unchecked_ref(), output.as_ref().unchecked_ref(),
)) ))?;
.unwrap();
audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000)); audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000));
console::log_1(&"Created Audio Decoder".into()); console::log_1(&"Created Audio Decoder".into());
@@ -341,22 +339,7 @@ fn create_decoder(audio_context: &AudioContext) -> AudioDecoder {
error.forget(); error.forget();
output.forget(); output.forget();
audio_decoder Ok(audio_decoder)
}
pub enum ConnectionState {
Disconnected,
Connecting,
Connected,
}
pub enum Command {
Connect {
address: String,
username: String,
hash: String,
},
Disconnect,
} }
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) { pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
@@ -370,180 +353,195 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
panic!("Did not receive connect command") panic!("Did not receive connect command")
}; };
*STATE.server.write() = Default::default(); if let Err(error) = network_connect(address, username, &mut event_rx).await {
*STATE.status.write() = ConnectionState::Connecting; console::error_1(&error);
console::log_1(&"Rust via WASM!".into());
let Ok(server_hash): Result<Vec<u8>, _> = 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!();
} }
}
}
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 = async fn network_connect(
match wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream()) address: String,
.await username: String,
{ event_rx: &mut UnboundedReceiver<Command>,
Ok(x) => x.into(), ) -> Result<(), JsValue> {
Err(e) => { *STATE.server.write() = Default::default();
console::log_1(&e.into()); *STATE.status.write() = ConnectionState::Connecting;
panic!();
}
};
let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into()); console::log_1(&"Rust via WASM!".into());
let wasm_stream_writable = wasm_streams::WritableStream::from_raw(stream.writable().into());
let read_codec = ClientControlCodec::new(); let Ok(server_hash): Result<Vec<u8>, _> = include_str!("../server_hash.txt")
let write_codec = ClientControlCodec::new(); .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 = let object = web_sys::js_sys::Object::new();
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 (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 { spawn(async move {
while let Ok(msg) = writer_recv_chan.recv().await { loop {
if let Err(e) = writer.send(msg).await { 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()); console::log_1(&e.to_string().into());
break; 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( fn accept_packet(
msg: ControlPacket<mumble_protocol::Clientbound>, msg: ControlPacket<mumble_protocol::Clientbound>,
audio_context: &AudioContext, audio_context: &AudioContext,
decoder_map: &mut HashMap<u32, AudioDecoder>, decoder_map: &mut HashMap<u32, AudioDecoder>,
) { ) -> Result<(), JsValue> {
if !matches!(msg, ControlPacket::UDPTunnel(_)) { if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) {
console::log_1(&format!("{:#?}", msg).into()); console::log_1(&format!("{:#?}", msg).into());
} }
match msg { match msg {
@@ -558,9 +556,12 @@ fn accept_packet(
position_info, position_info,
} => { } => {
// Get or create audio decoder for this user // Get or create audio decoder for this user
let audio_decoder = decoder_map let audio_decoder = match decoder_map.entry(session_id) {
.entry(session_id) Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
.or_insert_with(|| create_decoder(&audio_context)); Entry::Vacant(vacant_entry) => {
vacant_entry.insert(create_decoder(&audio_context)?)
}
};
// This will over time (as users join and leave) leak // This will over time (as users join and leave) leak
// AudioDecoders, MediaStreamTrackGenerators, MediaStreams, and MediaStreamAudioSourceNodes. // AudioDecoders, MediaStreamTrackGenerators, MediaStreams, and MediaStreamAudioSourceNodes.
// A better way to handle this would be to delete and create all the audio // 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. // any audio packets that come in the meantime.
if let VoicePacketPayload::Opus(audio_payload, end_bit) = payload { if let VoicePacketPayload::Opus(audio_payload, end_bit) = payload {
let js_audio_payload = Uint8Array::from(audio_payload.as_ref()); let js_audio_payload = Uint8Array::from(audio_payload.as_ref());
audio_decoder.decode( let _ = audio_decoder.decode(
&EncodedAudioChunk::new(&EncodedAudioChunkInit::new( &EncodedAudioChunk::new(&EncodedAudioChunkInit::new(
&js_audio_payload.into(), &js_audio_payload.into(),
0.0, 0.0,
@@ -668,10 +669,27 @@ fn accept_packet(
None None
}, },
dangerous_html: html_purifier::purifier(&text, Default::default()), 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(())
} }