From 110ce51c05ab29becfb01b1cc6a1a358631a33c3 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 12:43:00 +0100 Subject: [PATCH 001/130] + ws_stream_wasm, - dev-dependency on tokio/full --- Cargo.lock | 106 +++++++++++++++++++++++++--------- Cargo.toml | 2 +- examples/gateway_observers.rs | 2 +- examples/gateway_simple.rs | 2 +- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99ff172..d820185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,17 @@ dependencies = [ "syn 2.0.31", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + [[package]] name = "atoi" version = "2.0.0" @@ -209,6 +220,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "url", + "ws_stream_wasm", ] [[package]] @@ -533,6 +545,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -606,6 +633,7 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -730,12 +758,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - [[package]] name = "hex" version = "0.4.3" @@ -1175,16 +1197,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.1" @@ -1297,6 +1309,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -1603,6 +1625,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.11" @@ -1725,6 +1756,18 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.188" @@ -1841,15 +1884,6 @@ dependencies = [ "digest", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.1.0" @@ -2286,10 +2320,7 @@ dependencies = [ "bytes", "libc", "mio", - "num_cpus", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2 0.5.3", "tokio-macros", "windows-sys", @@ -2780,6 +2811,25 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 9df79d0..1919085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ chorus-macros = "0.2.0" rustls = "0.21.8" rustls-native-certs = "0.6.3" rand = "0.8.5" +ws_stream_wasm = "0.7.4" [dev-dependencies] -tokio = { version = "1.32.0", features = ["full"] } lazy_static = "1.4.0" rusty-hook = "0.11.2" diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 1572aa9..0a54d31 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -24,7 +24,7 @@ impl Observer for ExampleObserver { } } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { // Find the gateway websocket url of the server we want to connect to let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 26e8416..276d95f 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -4,7 +4,7 @@ use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { // Find the gateway websocket url of the server we want to connect to let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); From cc80bb50be3ecd9a1c1d0b27e70b7d0df5706640 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 12:57:17 +0100 Subject: [PATCH 002/130] Format changes --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1919085..95da49a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ website = ["https://discord.com/invite/m3FpcapGDD"] [features] default = ["client"] -backend = ["poem", "sqlx"] +backend = ["dep:poem", "dep:sqlx"] client = [] [dependencies] From af0178e15ab0d08d6a2d92eb446009c736731b3d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 13:55:55 +0100 Subject: [PATCH 003/130] compile-lock most wasm32 incompatibilities --- Cargo.lock | 615 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 40 ++-- 2 files changed, 335 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d820185..15c0a36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,21 +19,22 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -61,13 +62,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -119,9 +120,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -137,9 +138,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -155,27 +156,27 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ "libc", ] @@ -191,12 +192,13 @@ name = "chorus" version = "0.10.0" dependencies = [ "async-trait", - "base64 0.21.3", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "chorus-macros", "chrono", "custom_error", "futures-util", + "getrandom", "hostname", "http", "jsonwebtoken", @@ -231,21 +233,20 @@ checksum = "a81545a60b926f815517dadbbd40cd502294ae2baea25fa8194d854d607512b0" dependencies = [ "async-trait", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", "windows-targets", ] @@ -283,9 +284,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -301,9 +302,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crossbeam-queue" @@ -361,7 +362,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -372,7 +373,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -394,10 +395,11 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -455,25 +457,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "etcetera" version = "0.8.0" @@ -493,19 +484,24 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "pin-project", "spin 0.9.8", ] @@ -547,9 +543,9 @@ checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -562,9 +558,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -572,15 +568,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -600,38 +596,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -666,13 +662,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] @@ -708,9 +706,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash", "allocator-api2", @@ -722,7 +720,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.2", ] [[package]] @@ -731,7 +729,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -804,9 +802,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" dependencies = [ "bytes", "fnv", @@ -853,7 +851,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -875,16 +873,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -925,20 +923,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.2", "serde", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "ipnetwork" @@ -951,9 +949,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -966,9 +964,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -979,7 +977,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", "pem", "ring 0.16.20", "serde", @@ -1004,9 +1002,9 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" @@ -1021,15 +1019,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1049,18 +1047,19 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -1095,12 +1094,12 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys", ] @@ -1189,9 +1188,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -1214,11 +1213,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1235,7 +1234,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -1246,9 +1245,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.92" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -1268,9 +1267,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -1319,26 +1318,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "pin-project" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.31", -] - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1416,9 +1395,15 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1437,9 +1422,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1485,18 +1470,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1506,9 +1491,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1517,17 +1502,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1549,6 +1534,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -1599,16 +1585,14 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" dependencies = [ - "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", @@ -1636,11 +1620,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1673,11 +1657,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", ] [[package]] @@ -1725,12 +1709,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -1770,9 +1754,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -1790,20 +1774,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1812,13 +1796,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -1835,38 +1819,38 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.3", + "base64 0.21.5", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", + "indexmap 2.1.0", "serde", "serde_json", "serde_with_macros", - "time 0.3.28", + "time", ] [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1875,9 +1859,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -1886,9 +1870,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -1903,7 +1887,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.28", + "time", ] [[package]] @@ -1917,15 +1901,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1933,9 +1917,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -1968,9 +1952,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ "itertools", "nom", @@ -1979,9 +1963,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1992,9 +1976,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ "ahash", "atoi", @@ -2013,7 +1997,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.0", + "indexmap 2.1.0", "ipnetwork", "log", "memchr", @@ -2035,9 +2019,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" dependencies = [ "proc-macro2", "quote", @@ -2048,9 +2032,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" dependencies = [ "dotenvy", "either", @@ -2074,13 +2058,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" dependencies = [ "atoi", - "base64 0.21.3", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "bytes", "chrono", @@ -2117,13 +2101,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" dependencies = [ "atoi", - "base64 0.21.3", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "chrono", "crc", @@ -2158,9 +2142,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" dependencies = [ "atoi", "chrono", @@ -2181,10 +2165,11 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -2214,9 +2199,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -2224,10 +2209,31 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.8.0" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", @@ -2238,43 +2244,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -2282,15 +2278,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2312,29 +2308,29 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] @@ -2370,9 +2366,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -2385,9 +2381,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -2408,17 +2404,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -2431,11 +2427,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2444,20 +2439,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -2470,9 +2465,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", "bytes", @@ -2490,9 +2485,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uncased" @@ -2520,9 +2515,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2541,9 +2536,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode_categories" @@ -2601,12 +2596,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2615,9 +2604,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2625,24 +2614,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -2652,9 +2641,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2662,28 +2651,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -2718,10 +2707,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] @@ -2794,9 +2783,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -2830,6 +2819,26 @@ dependencies = [ "web-sys", ] +[[package]] +name = "zerocopy" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 95da49a..1c831eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ backend = ["dep:poem", "dep:sqlx"] client = [] [dependencies] -tokio = { version = "1.29.1", features = ["macros"] } +tokio = { version = "1.34.0", features = ["macros", "sync"] } serde = { version = "1.0.188", features = ["derive", "rc"] } serde_json = { version = "1.0.105", features = ["raw_value"] } serde-aux = "4.2.0" @@ -27,18 +27,28 @@ url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.4" custom_error = "1.9.2" -native-tls = "0.2.11" +futures-util = "0.3.28" +http = "0.2.9" +base64 = "0.21.3" +bitflags = { version = "2.4.0", features = ["serde"] } +lazy_static = "1.4.0" +poem = { version = "1.3.57", optional = true } +thiserror = "1.0.47" +jsonwebtoken = "8.3.0" +log = "0.4.20" +async-trait = "0.1.73" +chorus-macros = "0.2.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +rustls = "0.21.8" +rustls-native-certs = "0.6.3" +rand = "0.8.5" +hostname = "0.3.1" tokio-tungstenite = { version = "0.20.0", features = [ "rustls-tls-native-roots", "rustls-native-certs", ] } -futures-util = "0.3.28" -http = "0.2.9" -base64 = "0.21.3" -hostname = "0.3.1" -bitflags = { version = "2.4.0", features = ["serde"] } -lazy_static = "1.4.0" -poem = { version = "1.3.57", optional = true } +native-tls = "0.2.11" sqlx = { version = "0.7.1", features = [ "mysql", "sqlite", @@ -48,15 +58,11 @@ sqlx = { version = "0.7.1", features = [ "runtime-tokio-native-tls", "any", ], optional = true } -thiserror = "1.0.47" -jsonwebtoken = "8.3.0" -log = "0.4.20" -async-trait = "0.1.73" -chorus-macros = "0.2.0" -rustls = "0.21.8" -rustls-native-certs = "0.6.3" -rand = "0.8.5" + +[target.'cfg(target_arch = "wasm32")'.dependencies] ws_stream_wasm = "0.7.4" +getrandom = { version = "0.2.11", features = ["js"] } +tokio-tungstenite = { version = "0.20.0", default-features = false } [dev-dependencies] lazy_static = "1.4.0" From 7f3bb944b7f825f8a71e8329f82919bb21ddf7b7 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:18:22 +0100 Subject: [PATCH 004/130] make SentryConfiguration wasm-compatible - Moved `hostname` lib behind conditional compiling - provided alternative implementation of hostname::get for wasm-targets --- src/types/config/types/sentry_configuration.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/types/config/types/sentry_configuration.rs b/src/types/config/types/sentry_configuration.rs index 256bab9..99de4ff 100644 --- a/src/types/config/types/sentry_configuration.rs +++ b/src/types/config/types/sentry_configuration.rs @@ -11,6 +11,20 @@ pub struct SentryConfiguration { pub environment: String, } +impl SentryConfiguration { + #[cfg(not(target_arch = "wasm32"))] + fn get_hostname() -> std::io::Result { + hostname::get() + } + #[cfg(target_arch = "wasm32")] + fn get_hostname() -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Unsupported: wasm targets do not have a hostname", + )) + } +} + impl Default for SentryConfiguration { fn default() -> Self { Self { @@ -19,7 +33,7 @@ impl Default for SentryConfiguration { "https://241c6fb08adb469da1bb82522b25c99f@sentry.quartzinc.space/3", ), trace_sample_rate: 1.0, - environment: hostname::get() + environment: SentryConfiguration::get_hostname() .unwrap_or_else(|_| OsString::new()) .to_string_lossy() .to_string(), From aa657d12fc717ff78868f557f2bd2cbb5c63cb9d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:26:46 +0100 Subject: [PATCH 005/130] Ensure rt and rt_multi_thread aren't both enabled --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e63c41d..47bbaab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ clippy::new_without_default, clippy::useless_conversion )] +#[cfg(all(feature = "rt", feature = "rt_multi_thread"))] +compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); use url::{ParseError, Url}; From 9f81806ff11669bc8ea7c0683780fbbe4c4e7d00 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:29:37 +0100 Subject: [PATCH 006/130] remove sqlx from wasm-exclusion, add futures-timer --- Cargo.lock | 24 ++++++++++++++++++++++++ Cargo.toml | 27 +++++++++++++++------------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15c0a36..995aba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,7 @@ dependencies = [ "chorus-macros", "chrono", "custom_error", + "futures-timer", "futures-util", "getrandom", "hostname", @@ -623,6 +624,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.29" @@ -756,6 +763,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hex" version = "0.4.3" @@ -1196,6 +1209,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.1" @@ -2316,6 +2339,7 @@ dependencies = [ "bytes", "libc", "mio", + "num_cpus", "pin-project-lite", "socket2 0.5.5", "tokio-macros", diff --git a/Cargo.toml b/Cargo.toml index 1c831eb..9198461 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ website = ["https://discord.com/invite/m3FpcapGDD"] [features] -default = ["client"] +default = ["client", "rt-multi-thread"] backend = ["dep:poem", "dep:sqlx"] +rt-multi-thread = ["tokio/rt-multi-thread"] +rt = ["tokio/rt"] client = [] [dependencies] @@ -38,17 +40,6 @@ jsonwebtoken = "8.3.0" log = "0.4.20" async-trait = "0.1.73" chorus-macros = "0.2.0" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rustls = "0.21.8" -rustls-native-certs = "0.6.3" -rand = "0.8.5" -hostname = "0.3.1" -tokio-tungstenite = { version = "0.20.0", features = [ - "rustls-tls-native-roots", - "rustls-native-certs", -] } -native-tls = "0.2.11" sqlx = { version = "0.7.1", features = [ "mysql", "sqlite", @@ -58,6 +49,18 @@ sqlx = { version = "0.7.1", features = [ "runtime-tokio-native-tls", "any", ], optional = true } +futures-timer = "3.0.2" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +rustls = "0.21.8" +rustls-native-certs = "0.6.3" +rand = "0.8.5" +tokio-tungstenite = { version = "0.20.0", features = [ + "rustls-tls-native-roots", + "rustls-native-certs", +] } +native-tls = "0.2.11" +hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] ws_stream_wasm = "0.7.4" From 1b7450366f9bf03842214bf32d87e45111d70ecb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:38:20 +0100 Subject: [PATCH 007/130] Include `rand` crate in all targets --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9198461..7aed181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,11 +50,11 @@ sqlx = { version = "0.7.1", features = [ "any", ], optional = true } futures-timer = "3.0.2" +rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" rustls-native-certs = "0.6.3" -rand = "0.8.5" tokio-tungstenite = { version = "0.20.0", features = [ "rustls-tls-native-roots", "rustls-native-certs", From 641abe8ba69a10c51feee77f476127c599d1394d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 17:34:16 +0100 Subject: [PATCH 008/130] Use patched reqwest with multipart-headers support --- Cargo.lock | 7 +++---- Cargo.toml | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 995aba4..d83fe80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -815,9 +815,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1532,8 +1532,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" version = "0.11.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +source = "git+https://github.com/bitfl0wer/reqwest.git?branch=wasm-headers#4ab344a7a074ee2cebc3b0c1a9bee6f0337b8f1c" dependencies = [ "base64 0.21.5", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 7aed181..47b485a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,10 @@ serde_json = { version = "1.0.105", features = ["raw_value"] } serde-aux = "4.2.0" serde_with = "3.3.0" serde_repr = "0.1.16" -reqwest = { version = "0.11.20", features = ["multipart", "json"] } +reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [ + "multipart", + "json", +] } url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.4" From ec8d1c1f90ee177a67ddcf52a8d23066608e2c72 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 19:20:38 +0100 Subject: [PATCH 009/130] Replace tokio::time with safina-timer --- Cargo.lock | 17 ++++++++++------- Cargo.toml | 2 +- src/gateway.rs | 11 +++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d83fe80..a810276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,7 +197,6 @@ dependencies = [ "chorus-macros", "chrono", "custom_error", - "futures-timer", "futures-util", "getrandom", "hostname", @@ -213,6 +212,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rusty-hook", + "safina-timer", "serde", "serde-aux", "serde_json", @@ -624,12 +624,6 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.29" @@ -1714,6 +1708,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "safina-timer" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1081a264d1a3e81b75c4bcd5696094fb6ce470c2ded14cbd47bcb5229079b9df" +dependencies = [ + "once_cell", +] + [[package]] name = "schannel" version = "0.1.22" diff --git a/Cargo.toml b/Cargo.toml index 47b485a..df16dfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ sqlx = { version = "0.7.1", features = [ "runtime-tokio-native-tls", "any", ], optional = true } -futures-timer = "3.0.2" +safina-timer = "0.1.11" rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/gateway.rs b/src/gateway.rs index edd402d..4229902 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -12,8 +12,7 @@ use std::any::Any; use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, RwLock}; -use std::time::Duration; -use tokio::time::sleep_until; +use std::time::{self, Duration}; use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; @@ -25,8 +24,6 @@ use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; use tokio::task; use tokio::task::JoinHandle; -use tokio::time; -use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; @@ -798,10 +795,12 @@ impl HeartbeatHandler { mut receive: tokio::sync::mpsc::Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); + let mut last_heartbeat_timestamp: time::Instant = time::Instant::now(); let mut last_heartbeat_acknowledged = true; let mut last_seq_number: Option = None; + safina_timer::start_timer_thread(); + loop { if kill_receive.try_recv().is_ok() { trace!("GW: Closing heartbeat task"); @@ -818,7 +817,7 @@ impl HeartbeatHandler { let mut should_send = false; tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { + () = safina_timer::sleep_until(last_heartbeat_timestamp + timeout) => { should_send = true; } Some(communication) = receive.recv() => { From 572a586505b3dc8252246c37d5b74d53f4576710 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 16:42:01 +0100 Subject: [PATCH 010/130] I fucked up the merge and merged gateway wrongly --- src/gateway/gateway.rs | 577 +---------------------------------------- 1 file changed, 9 insertions(+), 568 deletions(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index edd402d..30d0610 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,331 +1,10 @@ -//! Gateway connection, communication and handling, as well as object caching and updating. - -use crate::errors::GatewayError; -use crate::gateway::events::Events; +use self::event::Events; +use super::*; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, - ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, - Snowflake, SourceUrlField, ThreadUpdate, UpdateMessage, WebSocketEvent, + ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, + ThreadUpdate, UpdateMessage, WebSocketEvent, }; -use async_trait::async_trait; -use std::any::Any; -use std::collections::HashMap; -use std::fmt::Debug; -use std::sync::{Arc, RwLock}; -use std::time::Duration; -use tokio::time::sleep_until; - -use futures_util::stream::SplitSink; -use futures_util::stream::SplitStream; -use futures_util::SinkExt; -use futures_util::StreamExt; -use log::{info, trace, warn}; -use tokio::net::TcpStream; -use tokio::sync::mpsc::Sender; -use tokio::sync::Mutex; -use tokio::task; -use tokio::task::JoinHandle; -use tokio::time; -use tokio::time::Instant; -use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; - -// Gateway opcodes -/// Opcode received when the server dispatches a [crate::types::WebSocketEvent] -const GATEWAY_DISPATCH: u8 = 0; -/// Opcode sent when sending a heartbeat -const GATEWAY_HEARTBEAT: u8 = 1; -/// Opcode sent to initiate a session -/// -/// See [types::GatewayIdentifyPayload] -const GATEWAY_IDENTIFY: u8 = 2; -/// Opcode sent to update our presence -/// -/// See [types::GatewayUpdatePresence] -const GATEWAY_UPDATE_PRESENCE: u8 = 3; -/// Opcode sent to update our state in vc -/// -/// Like muting, deafening, leaving, joining.. -/// -/// See [types::UpdateVoiceState] -const GATEWAY_UPDATE_VOICE_STATE: u8 = 4; -/// Opcode sent to resume a session -/// -/// See [types::GatewayResume] -const GATEWAY_RESUME: u8 = 6; -/// Opcode received to tell the client to reconnect -const GATEWAY_RECONNECT: u8 = 7; -/// Opcode sent to request guild member data -/// -/// See [types::GatewayRequestGuildMembers] -const GATEWAY_REQUEST_GUILD_MEMBERS: u8 = 8; -/// Opcode received to tell the client their token / session is invalid -const GATEWAY_INVALID_SESSION: u8 = 9; -/// Opcode received when initially connecting to the gateway, starts our heartbeat -/// -/// See [types::HelloData] -const GATEWAY_HELLO: u8 = 10; -/// Opcode received to acknowledge a heartbeat -const GATEWAY_HEARTBEAT_ACK: u8 = 11; -/// Opcode sent to get the voice state of users in a given DM/group channel -/// -/// See [types::CallSync] -const GATEWAY_CALL_SYNC: u8 = 13; -/// Opcode sent to get data for a server (Lazy Loading request) -/// -/// Sent by the official client when switching to a server -/// -/// See [types::LazyRequest] -const GATEWAY_LAZY_REQUEST: u8 = 14; - -/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; - -/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. -/// This struct is used internally when handling messages. -#[derive(Clone, Debug)] -pub struct GatewayMessage { - /// The message we received from the server - message: tokio_tungstenite::tungstenite::Message, -} - -impl GatewayMessage { - /// Creates self from a tungstenite message - pub fn from_tungstenite_message(message: tokio_tungstenite::tungstenite::Message) -> Self { - Self { message } - } - - /// Parses the message as an error; - /// Returns the error if succesfully parsed, None if the message isn't an error - pub fn error(&self) -> Option { - let content = self.message.to_string(); - - // Some error strings have dots on the end, which we don't care about - let processed_content = content.to_lowercase().replace('.', ""); - - match processed_content.as_str() { - "unknown error" | "4000" => Some(GatewayError::Unknown), - "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcode), - "decode error" | "error while decoding payload" | "4002" => Some(GatewayError::Decode), - "not authenticated" | "4003" => Some(GatewayError::NotAuthenticated), - "authentication failed" | "4004" => Some(GatewayError::AuthenticationFailed), - "already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticated), - "invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumber), - "rate limited" | "4008" => Some(GatewayError::RateLimited), - "session timed out" | "4009" => Some(GatewayError::SessionTimedOut), - "invalid shard" | "4010" => Some(GatewayError::InvalidShard), - "sharding required" | "4011" => Some(GatewayError::ShardingRequired), - "invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersion), - "invalid intent(s)" | "invalid intent" | "4013" => Some(GatewayError::InvalidIntents), - "disallowed intent(s)" | "disallowed intents" | "4014" => { - Some(GatewayError::DisallowedIntents) - } - _ => None, - } - } - - /// Returns whether or not the message is an error - pub fn is_error(&self) -> bool { - self.error().is_some() - } - - /// Parses the message as a payload; - /// Returns a result of deserializing - pub fn payload(&self) -> Result { - return serde_json::from_str(self.message.to_text().unwrap()); - } - - /// Returns whether or not the message is a payload - pub fn is_payload(&self) -> bool { - // close messages are never payloads, payloads are only text messages - if self.message.is_close() | !self.message.is_text() { - return false; - } - - return self.payload().is_ok(); - } - - /// Returns whether or not the message is empty - pub fn is_empty(&self) -> bool { - self.message.is_empty() - } -} - -pub type ObservableObject = dyn Send + Sync + Any; - -/// Represents a handle to a Gateway connection. A Gateway connection will create observable -/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently -/// implemented types with the trait [`WebSocketEvent`] -/// Using this handle you can also send Gateway Events directly. -#[derive(Debug, Clone)] -pub struct GatewayHandle { - pub url: String, - pub events: Arc>, - pub websocket_send: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - /// Tells gateway tasks to close - kill_send: tokio::sync::broadcast::Sender<()>, - pub(crate) store: Arc>>>>, -} - -/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. -pub trait Updateable: 'static + Send + Sync { - fn id(&self) -> Snowflake; -} - -impl GatewayHandle { - /// Sends json to the gateway with an opcode - async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { - let gateway_payload = types::GatewaySendPayload { - op_code, - event_data: Some(to_send), - sequence_number: None, - }; - - let payload_json = serde_json::to_string(&gateway_payload).unwrap(); - - let message = tokio_tungstenite::tungstenite::Message::text(payload_json); - - self.websocket_send - .lock() - .await - .send(message) - .await - .unwrap(); - } - - pub async fn observe>( - &self, - object: Arc>, - ) -> Arc> { - let mut store = self.store.lock().await; - let id = object.read().unwrap().id(); - if let Some(channel) = store.get(&id) { - let object = channel.clone(); - drop(store); - object - .read() - .unwrap() - .downcast_ref::() - .unwrap_or_else(|| { - panic!( - "Snowflake {} already exists in the store, but it is not of type T.", - id - ) - }); - let ptr = Arc::into_raw(object.clone()); - // SAFETY: - // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. - // - This operation doesn't read or write any shared data, and thus cannot cause a data race - // - The reference count is not being modified - let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock).clone() }; - let object = downcasted.read().unwrap().clone(); - - let watched_object = object.watch_whole(self).await; - *downcasted.write().unwrap() = watched_object; - downcasted - } else { - let id = object.read().unwrap().id(); - let object = object.read().unwrap().clone(); - let object = object.clone().watch_whole(self).await; - let wrapped = Arc::new(RwLock::new(object)); - store.insert(id, wrapped.clone()); - wrapped - } - } - - /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` - /// with all of its observable fields being observed. - pub async fn observe_and_into_inner>( - &self, - object: Arc>, - ) -> T { - let channel = self.observe(object.clone()).await; - let object = channel.read().unwrap().clone(); - object - } - - /// Sends an identify event to the gateway - pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Identify.."); - - self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; - } - - /// Sends a resume event to the gateway - pub async fn send_resume(&self, to_send: types::GatewayResume) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Resume.."); - - self.send_json_event(GATEWAY_RESUME, to_send_value).await; - } - - /// Sends an update presence event to the gateway - pub async fn send_update_presence(&self, to_send: types::UpdatePresence) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Update Presence.."); - - self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) - .await; - } - - /// Sends a request guild members to the server - pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Request Guild Members.."); - - self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) - .await; - } - - /// Sends an update voice state to the server - pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { - let to_send_value = serde_json::to_value(to_send).unwrap(); - - trace!("GW: Sending Update Voice State.."); - - self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) - .await; - } - - /// Sends a call sync to the server - pub async fn send_call_sync(&self, to_send: types::CallSync) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Call Sync.."); - - self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; - } - - /// Sends a Lazy Request - pub async fn send_lazy_request(&self, to_send: types::LazyRequest) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Lazy Request.."); - - self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) - .await; - } - - /// Closes the websocket connection and stops all gateway tasks; - /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; - pub async fn close(&self) { - self.kill_send.send(()).unwrap(); - self.websocket_send.lock().await.close().await.unwrap(); - } -} #[derive(Debug)] pub struct Gateway { @@ -709,10 +388,10 @@ impl Gateway { | GATEWAY_REQUEST_GUILD_MEMBERS | GATEWAY_CALL_SYNC | GATEWAY_LAZY_REQUEST => { - let error = GatewayError::UnexpectedOpcodeReceived { - opcode: gateway_payload.op_code, - }; - Err::<(), GatewayError>(error).unwrap(); + info!( + "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", + gateway_payload.op_code + ); } _ => { warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); @@ -736,196 +415,7 @@ impl Gateway { } } -/// Handles sending heartbeats to the gateway in another thread -#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used -#[derive(Debug)] -struct HeartbeatHandler { - /// How ofter heartbeats need to be sent at a minimum - pub heartbeat_interval: Duration, - /// The send channel for the heartbeat thread - pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, -} - -impl HeartbeatHandler { - pub fn new( - heartbeat_interval: Duration, - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { - let (send, receive) = tokio::sync::mpsc::channel(32); - let kill_receive = kill_rc.resubscribe(); - - let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; - }); - - Self { - heartbeat_interval, - send, - handle, - } - } - - /// The main heartbeat task; - /// - /// Can be killed by the kill broadcast; - /// If the websocket is closed, will die out next time it tries to send a heartbeat; - pub async fn heartbeat_task( - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, - mut kill_receive: tokio::sync::broadcast::Receiver<()>, - ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); - let mut last_heartbeat_acknowledged = true; - let mut last_seq_number: Option = None; - - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - - let timeout = if last_heartbeat_acknowledged { - heartbeat_interval - } else { - // If the server hasn't acknowledged our heartbeat we should resend it - Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) - }; - - let mut should_send = false; - - tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { - should_send = true; - } - Some(communication) = receive.recv() => { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; - } - - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; - } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} - } - } - } - } - - if should_send { - trace!("GW: Sending Heartbeat.."); - - let heartbeat = types::GatewayHeartbeat { - op: GATEWAY_HEARTBEAT, - d: last_seq_number, - }; - - let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - - let send_result = websocket_tx.lock().await.send(msg).await; - if send_result.is_err() { - // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); - break; - } - - last_heartbeat_timestamp = time::Instant::now(); - last_heartbeat_acknowledged = false; - } - } - } -} - -/// Used for communications between the heartbeat and gateway thread. -/// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server -#[derive(Clone, Copy, Debug)] -struct HeartbeatThreadCommunication { - /// The opcode for the communication we received, if relevant - op_code: Option, - /// The sequence number we got from discord, if any - sequence_number: Option, -} - -/// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to -/// an Observable. The Observer is notified when the Observable's data changes. -/// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. -/// Note that `Debug` is used to tell `Observer`s apart when unsubscribing. -#[async_trait] -pub trait Observer: Sync + Send + std::fmt::Debug { - async fn update(&self, data: &T); -} - -/// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a -/// change in the WebSocketEvent. GatewayEvents are observable. -#[derive(Default, Debug)] -pub struct GatewayEvent { - observers: Vec>>, -} - -impl GatewayEvent { - /// Returns true if the GatewayEvent is observed by at least one Observer. - pub fn is_observed(&self) -> bool { - !self.observers.is_empty() - } - - /// Subscribes an Observer to the GatewayEvent. - pub fn subscribe(&mut self, observable: Arc>) { - self.observers.push(observable); - } - - /// Unsubscribes an Observer from the GatewayEvent. - pub fn unsubscribe(&mut self, observable: &dyn Observer) { - // .retain()'s closure retains only those elements of the vector, which have a different - // pointer value than observable. - // The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same - // anddd there is no way to do that without using format - let to_remove = format!("{:?}", observable); - self.observers - .retain(|obs| format!("{:?}", obs) != to_remove); - } - - /// Notifies the observers of the GatewayEvent. - async fn notify(&self, new_event_data: T) { - for observer in &self.observers { - observer.update(&new_event_data).await; - } - } -} - -pub mod events { +pub mod event { use super::*; #[derive(Default, Debug)] @@ -1086,52 +576,3 @@ pub mod events { pub update: GatewayEvent, } } - -#[cfg(test)] -mod example { - use super::*; - use std::sync::atomic::{AtomicI32, Ordering::Relaxed}; - - #[derive(Debug)] - struct Consumer { - _name: String, - events_received: AtomicI32, - } - - #[async_trait] - impl Observer for Consumer { - async fn update(&self, _data: &types::GatewayResume) { - self.events_received.fetch_add(1, Relaxed); - } - } - - #[tokio::test] - async fn test_observer_behavior() { - let mut event = GatewayEvent::default(); - - let new_data = types::GatewayResume { - token: "token_3276ha37am3".to_string(), - session_id: "89346671230".to_string(), - seq: "3".to_string(), - }; - - let consumer = Arc::new(Consumer { - _name: "first".into(), - events_received: 0.into(), - }); - event.subscribe(consumer.clone()); - - let second_consumer = Arc::new(Consumer { - _name: "second".into(), - events_received: 0.into(), - }); - event.subscribe(second_consumer.clone()); - - event.notify(new_data.clone()).await; - event.unsubscribe(&*consumer); - event.notify(new_data).await; - - assert_eq!(consumer.events_received.load(Relaxed), 1); - assert_eq!(second_consumer.events_received.load(Relaxed), 2); - } -} From 99eb869878543745f95abd9409b8ec87d12f126c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 19:00:43 +0100 Subject: [PATCH 011/130] Remove unneeded import --- src/types/entities/voice_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 6bc45e2..1c36268 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, RwLock}; #[cfg(feature = "client")] -use chorus_macros::{Composite, Updateable}; +use chorus_macros::Composite; #[cfg(feature = "client")] use crate::types::Composite; From 199bf91daf9079653f22356efd98a1623170bfe5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:04:23 +0100 Subject: [PATCH 012/130] Factor out GatewayStore into own type --- src/gateway/gateway.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 30d0610..edccd11 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -6,6 +6,8 @@ use crate::types::{ ThreadUpdate, UpdateMessage, WebSocketEvent, }; +pub type GatewayStore = Arc>>>>; + #[derive(Debug)] pub struct Gateway { events: Arc>, From 03d3c0b112933f48b4e85e98c75ca4e77ef45b86 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:05:21 +0100 Subject: [PATCH 013/130] Use GatewayStore instead of complex typedef --- src/gateway/gateway.rs | 2 +- src/gateway/handle.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index edccd11..b4149db 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -22,7 +22,7 @@ pub struct Gateway { >, websocket_receive: SplitStream>>, kill_send: tokio::sync::broadcast::Sender<()>, - store: Arc>>>>, + store: GatewayStore, url: String, } diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 1200b30..a4cf20a 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -19,7 +19,7 @@ pub struct GatewayHandle { >, /// Tells gateway tasks to close pub(super) kill_send: tokio::sync::broadcast::Sender<()>, - pub(crate) store: Arc>>>>, + pub(crate) store: GatewayStore, } impl GatewayHandle { From 2cb35f2f141e84f1e557aa5622fc5fdbad8531d5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:05:47 +0100 Subject: [PATCH 014/130] Start impl of Gateway Traits --- src/gateway/mod.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index ebd06cc..b6bd031 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -7,6 +7,7 @@ pub use gateway::*; pub use handle::*; use heartbeat::*; pub use message::*; +use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; use crate::types::{Snowflake, WebSocketEvent}; @@ -21,8 +22,8 @@ use tokio::time::sleep_until; use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; -use futures_util::SinkExt; -use futures_util::StreamExt; +use futures_util::{Sink, StreamExt}; +use futures_util::{SinkExt, Stream}; use log::{info, trace, warn}; use tokio::net::TcpStream; use tokio::sync::mpsc::Sender; @@ -34,6 +35,8 @@ use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; +use self::event::Events; + // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] const GATEWAY_DISPATCH: u8 = 0; @@ -135,8 +138,35 @@ impl GatewayEvent { } } +#[allow(clippy::type_complexity)] +pub trait GatewayCapable +where + R: Stream, + S: Sink, +{ + fn get_events(&self) -> Arc>; + fn get_websocket_send(&self) -> Arc>>; + fn get_store(&self) -> GatewayStore; + fn get_url(&self) -> String; + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + /// TODO: Explain what this method has to do to be a good new() impl, or link to such documentation + fn new( + &self, + websocket_url: &'static str, + ) -> Result>, R, S>>, GatewayError>; +} + +pub trait GatewayHandleCapable +where + T: GatewayCapable, + R: Stream, + S: Sink, +{ +} + #[cfg(test)] -mod example { +mod test { use crate::types; use super::*; From ee0169c648ea2a749c3339917fe2cc1ffffe42cd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:46:46 +0100 Subject: [PATCH 015/130] Add missing, least required methods to traitdef --- src/gateway/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index b6bd031..04043e9 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -148,13 +148,15 @@ where fn get_websocket_send(&self) -> Arc>>; fn get_store(&self) -> GatewayStore; fn get_url(&self) -> String; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - /// TODO: Explain what this method has to do to be a good new() impl, or link to such documentation - fn new( + /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] + /// + /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation + fn get_handle( &self, websocket_url: &'static str, ) -> Result>, R, S>>, GatewayError>; + fn close(&mut self); + fn handle_message(&mut self, msg: GatewayMessage); } pub trait GatewayHandleCapable From 1128502b800508a672712cc9fc3050c2bf9465a4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 21:14:05 +0100 Subject: [PATCH 016/130] start converting struct impl into trait impl --- src/gateway/gateway.rs | 48 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index b4149db..7aaae67 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -26,9 +26,14 @@ pub struct Gateway { url: String, } -impl Gateway { +impl + GatewayCapable< + WebSocketStream>, + WebSocketStream>, + > for Gateway +{ #[allow(clippy::new_ret_no_self)] - pub async fn new(websocket_url: String) -> Result { + async fn new(websocket_url: String) -> Result { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { @@ -415,6 +420,45 @@ impl Gateway { .unwrap(); } } + + fn get_events(&self) -> Arc> { + self.events.clone() + } + + fn get_websocket_send( + &self, + ) -> Arc>, Message>>> { + self.websocket_send.clone() + } + + fn get_store(&self) -> GatewayStore { + self.store.clone() + } + + fn get_url(&self) -> String { + self.url + } + + fn get_handle( + &self, + websocket_url: &'static str, + ) -> Result< + Box< + dyn GatewayHandleCapable< + Box< + dyn GatewayCapable< + WebSocketStream>, + WebSocketStream>, + >, + >, + WebSocketStream>, + WebSocketStream>, + >, + >, + GatewayError, + > { + todo!() + } } pub mod event { From 06d25d3e500bba73209bf1340761d89ed97860bf Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 00:04:04 +0100 Subject: [PATCH 017/130] Impl base of GatewayCapable for Gateway --- src/api/auth/login.rs | 4 +- src/api/auth/mod.rs | 3 +- src/api/auth/register.rs | 4 +- src/gateway/gateway.rs | 100 ++++++-------- src/gateway/handle.rs | 8 ++ src/gateway/mod.rs | 275 +++++++++++++++++++++++++++++++++++++-- src/instance.rs | 4 +- 7 files changed, 322 insertions(+), 76 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 272ee04..750d01e 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -4,7 +4,7 @@ use reqwest::Client; use serde_json::to_string; use crate::errors::ChorusResult; -use crate::gateway::Gateway; +use crate::gateway::{Gateway, GatewayCapable}; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; @@ -36,7 +36,7 @@ impl Instance { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + let gateway = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); identify.token = login_result.token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 3ad4a60..091975c 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; pub use login::*; pub use register::*; +use crate::gateway::GatewayCapable; use crate::{ errors::ChorusResult, gateway::Gateway, @@ -25,7 +26,7 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + let gateway = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index d10915e..ed6fcbf 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; use reqwest::Client; use serde_json::to_string; -use crate::gateway::Gateway; +use crate::gateway::{Gateway, GatewayCapable, GatewayHandle}; use crate::types::GatewayIdentifyPayload; use crate::{ errors::ChorusResult, @@ -45,7 +45,7 @@ impl Instance { let user_object = self.get_user(token.clone(), None).await.unwrap(); let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), &mut self).await?; let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + let gateway: GatewayHandle = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 7aaae67..15215ac 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -26,14 +26,16 @@ pub struct Gateway { url: String, } +#[async_trait] impl GatewayCapable< WebSocketStream>, WebSocketStream>, + GatewayHandle, > for Gateway { #[allow(clippy::new_ret_no_self)] - async fn new(websocket_url: String) -> Result { + async fn get_handle(websocket_url: String) -> Result { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { @@ -118,51 +120,14 @@ impl }) } - /// The main gateway listener task; - /// - /// Can only be stopped by closing the websocket, cannot be made to listen for kill - pub async fn gateway_listen_task(&mut self) { - loop { - let msg = self.websocket_receive.next().await; - - // This if chain can be much better but if let is unstable on stable rust - if let Some(Ok(message)) = msg { - self.handle_message(GatewayMessage::from_tungstenite_message(message)) - .await; - continue; - } - - // We couldn't receive the next message or it was an error, something is wrong with the websocket, close - warn!("GW: Websocket is broken, stopping gateway"); - break; - } - } - /// Closes the websocket connection and stops all tasks async fn close(&mut self) { self.kill_send.send(()).unwrap(); self.websocket_send.lock().await.close().await.unwrap(); } - /// Deserializes and updates a dispatched event, when we already know its type; - /// (Called for every event in handle_message) - #[allow(dead_code)] // TODO: Remove this allow annotation - async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( - data: &'a str, - event: &mut GatewayEvent, - ) -> Result<(), serde_json::Error> { - let data_deserialize_result: Result = serde_json::from_str(data); - - if data_deserialize_result.is_err() { - return Err(data_deserialize_result.err().unwrap()); - } - - event.notify(data_deserialize_result.unwrap()).await; - Ok(()) - } - /// This handles a message as a websocket event and updates its events along with the events' observers - pub async fn handle_message(&mut self, msg: GatewayMessage) { + async fn handle_message(&mut self, msg: GatewayMessage) { if msg.is_empty() { return; } @@ -436,28 +401,45 @@ impl } fn get_url(&self) -> String { - self.url + self.url.clone() + } +} + +impl Gateway { + /// The main gateway listener task; + /// + /// Can only be stopped by closing the websocket, cannot be made to listen for kill + pub async fn gateway_listen_task(&mut self) { + loop { + let msg = self.websocket_receive.next().await; + + // This if chain can be much better but if let is unstable on stable rust + if let Some(Ok(message)) = msg { + self.handle_message(GatewayMessage::from_tungstenite_message(message)); + continue; + } + + // We couldn't receive the next message or it was an error, something is wrong with the websocket, close + warn!("GW: Websocket is broken, stopping gateway"); + break; + } } - fn get_handle( - &self, - websocket_url: &'static str, - ) -> Result< - Box< - dyn GatewayHandleCapable< - Box< - dyn GatewayCapable< - WebSocketStream>, - WebSocketStream>, - >, - >, - WebSocketStream>, - WebSocketStream>, - >, - >, - GatewayError, - > { - todo!() + /// Deserializes and updates a dispatched event, when we already know its type; + /// (Called for every event in handle_message) + #[allow(dead_code)] // TODO: Remove this allow annotation + async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( + data: &'a str, + event: &mut GatewayEvent, + ) -> Result<(), serde_json::Error> { + let data_deserialize_result: Result = serde_json::from_str(data); + + if data_deserialize_result.is_err() { + return Err(data_deserialize_result.err().unwrap()); + } + + event.notify(data_deserialize_result.unwrap()).await; + Ok(()) } } diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index a4cf20a..3a87c5a 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -169,3 +169,11 @@ impl GatewayHandle { self.websocket_send.lock().await.close().await.unwrap(); } } + +impl + GatewayHandleCapable< + WebSocketStream>, + WebSocketStream>, + > for GatewayHandle +{ +} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 04043e9..2b5d414 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -10,7 +10,7 @@ pub use message::*; use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; -use crate::types::{Snowflake, WebSocketEvent}; +use crate::types::{self, Snowflake, WebSocketEvent}; use async_trait::async_trait; use std::any::Any; @@ -139,10 +139,12 @@ impl GatewayEvent { } #[allow(clippy::type_complexity)] -pub trait GatewayCapable +#[async_trait] +pub trait GatewayCapable where R: Stream, S: Sink, + G: GatewayHandleCapable, { fn get_events(&self) -> Arc>; fn get_websocket_send(&self) -> Arc>>; @@ -151,17 +153,270 @@ where /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] /// /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - fn get_handle( - &self, - websocket_url: &'static str, - ) -> Result>, R, S>>, GatewayError>; - fn close(&mut self); - fn handle_message(&mut self, msg: GatewayMessage); + async fn get_handle(websocket_url: String) -> Result; + async fn close(&mut self); + async fn handle_message(&mut self, msg: GatewayMessage) { + if msg.is_empty() { + return; + } + + if !msg.is_error() && !msg.is_payload() { + warn!( + "Message unrecognised: {:?}, please open an issue on the chorus github", + msg.message.to_string() + ); + return; + } + + if msg.is_error() { + let error = msg.error().unwrap(); + + warn!("GW: Received error {:?}, connection will close..", error); + + self.close().await; + + self.get_events().lock().await.error.notify(error).await; + + return; + } + + let gateway_payload = msg.payload().unwrap(); + + // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes + match gateway_payload.op_code { + // An event was dispatched, we need to look at the gateway event name t + GATEWAY_DISPATCH => { + let Some(event_name) = gateway_payload.event_name else { + warn!("Gateway dispatch op without event_name"); + return; + }; + + trace!("Gateway: Received {event_name}"); + + macro_rules! handle { + ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { + match event_name.as_str() { + $($name => { + let event = &mut self.get_events().lock().await.$($path).+; + let json = gateway_payload.event_data.unwrap().get(); + match serde_json::from_str(json) { + Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), + Ok(message) => { + $( + let mut message: $message_type = message; + let store = self.get_store().lock().await; + let id = if message.id().is_some() { + message.id().unwrap() + } else { + event.notify(message).await; + return; + }; + if let Some(to_update) = store.get(&id) { + let object = to_update.clone(); + let inner_object = object.read().unwrap(); + if let Some(_) = inner_object.downcast_ref::<$update_type>() { + let ptr = Arc::into_raw(object.clone()); + // SAFETY: + // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. + // - This operation doesn't read or write any shared data, and thus cannot cause a data race + // - The reference count is not being modified + let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; + drop(inner_object); + message.set_json(json.to_string()); + message.set_source_url(self.get_url().clone()); + message.update(downcasted.clone()); + } else { + warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) + } + } + )? + event.notify(message).await; + } + } + },)* + "RESUMED" => (), + "SESSIONS_REPLACE" => { + let result: Result, serde_json::Error> = + serde_json::from_str(gateway_payload.event_data.unwrap().get()); + match result { + Err(err) => { + warn!( + "Failed to parse gateway event {} ({})", + event_name, + err + ); + return; + } + Ok(sessions) => { + self.events.lock().await.session.replace.notify( + types::SessionsReplace {sessions} + ).await; + } + } + }, + _ => { + warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); + } + } + }; + } + + // See https://discord.com/developers/docs/topics/gateway-events#receive-events + // "Some" of these are undocumented + handle!( + "READY" => session.ready, + "READY_SUPPLEMENTAL" => session.ready_supplemental, + "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, + "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, + "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule, + "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, + "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, + "CHANNEL_CREATE" => channel.create ChannelCreate: Guild, + "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, + "CHANNEL_UNREAD_UPDATE" => channel.unread_update, + "CHANNEL_DELETE" => channel.delete ChannelDelete: Guild, + "CHANNEL_PINS_UPDATE" => channel.pins_update, + "CALL_CREATE" => call.create, + "CALL_UPDATE" => call.update, + "CALL_DELETE" => call.delete, + "THREAD_CREATE" => thread.create, // TODO + "THREAD_UPDATE" => thread.update ThreadUpdate: Channel, + "THREAD_DELETE" => thread.delete, // TODO + "THREAD_LIST_SYNC" => thread.list_sync, // TODO + "THREAD_MEMBER_UPDATE" => thread.member_update, // TODO + "THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO + "GUILD_CREATE" => guild.create, // TODO + "GUILD_UPDATE" => guild.update, // TODO + "GUILD_DELETE" => guild.delete, // TODO + "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, + "GUILD_BAN_ADD" => guild.ban_add, // TODO + "GUILD_BAN_REMOVE" => guild.ban_remove, // TODO + "GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO + "GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO + "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, + "GUILD_MEMBER_ADD" => guild.member_add, + "GUILD_MEMBER_REMOVE" => guild.member_remove, + "GUILD_MEMBER_UPDATE" => guild.member_update, // TODO + "GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO + "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild, + "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject, + "GUILD_ROLE_DELETE" => guild.role_delete, // TODO + "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO + "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO + "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO + "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, + "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, + "PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO + "INTEGRATION_CREATE" => integration.create, // TODO + "INTEGRATION_UPDATE" => integration.update, // TODO + "INTEGRATION_DELETE" => integration.delete, // TODO + "INTERACTION_CREATE" => interaction.create, // TODO + "INVITE_CREATE" => invite.create, // TODO + "INVITE_DELETE" => invite.delete, // TODO + "MESSAGE_CREATE" => message.create, + "MESSAGE_UPDATE" => message.update, // TODO + "MESSAGE_DELETE" => message.delete, + "MESSAGE_DELETE_BULK" => message.delete_bulk, + "MESSAGE_REACTION_ADD" => message.reaction_add, // TODO + "MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO + "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO + "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO + "MESSAGE_ACK" => message.ack, + "PRESENCE_UPDATE" => user.presence_update, // TODO + "RELATIONSHIP_ADD" => relationship.add, + "RELATIONSHIP_REMOVE" => relationship.remove, + "STAGE_INSTANCE_CREATE" => stage_instance.create, + "STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO + "STAGE_INSTANCE_DELETE" => stage_instance.delete, + "TYPING_START" => user.typing_start, + "USER_UPDATE" => user.update, // TODO + "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, + "VOICE_STATE_UPDATE" => voice.state_update, // TODO + "VOICE_SERVER_UPDATE" => voice.server_update, + "WEBHOOKS_UPDATE" => webhooks.update + ); + } + // We received a heartbeat from the server + // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." + GATEWAY_HEARTBEAT => { + trace!("GW: Received Heartbeat // Heartbeat Request"); + + // Tell the heartbeat handler it should send a heartbeat right away + + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: gateway_payload.sequence_number, + op_code: Some(GATEWAY_HEARTBEAT), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + GATEWAY_RECONNECT => { + todo!() + } + GATEWAY_INVALID_SESSION => { + todo!() + } + // Starts our heartbeat + // We should have already handled this in gateway init + GATEWAY_HELLO => { + warn!("Received hello when it was unexpected"); + } + GATEWAY_HEARTBEAT_ACK => { + trace!("GW: Received Heartbeat ACK"); + + // Tell the heartbeat handler we received an ack + + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: gateway_payload.sequence_number, + op_code: Some(GATEWAY_HEARTBEAT_ACK), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + GATEWAY_IDENTIFY + | GATEWAY_UPDATE_PRESENCE + | GATEWAY_UPDATE_VOICE_STATE + | GATEWAY_RESUME + | GATEWAY_REQUEST_GUILD_MEMBERS + | GATEWAY_CALL_SYNC + | GATEWAY_LAZY_REQUEST => { + info!( + "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", + gateway_payload.op_code + ); + } + _ => { + warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + } + } + + // If we we received a seq number we should let it know + if let Some(seq_num) = gateway_payload.sequence_number { + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: Some(seq_num), + // Op code is irrelevant here + op_code: None, + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + } } -pub trait GatewayHandleCapable +pub trait GatewayHandleCapable where - T: GatewayCapable, R: Stream, S: Sink, { diff --git a/src/instance.rs b/src/instance.rs index 72bf350..8dc9e2b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -9,7 +9,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayHandle}; +use crate::gateway::{Gateway, GatewayCapable, GatewayHandle}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, Limit, LimitType, User, UserSettings}; @@ -138,7 +138,7 @@ impl ChorusUser { let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); // Dummy gateway object - let gateway = Gateway::new(wss_url).await.unwrap(); + let gateway = Gateway::get_handle(wss_url).await.unwrap(); ChorusUser { token, belongs_to: instance.clone(), From 5af6d1ce4b2b72000d35e27cb173485e279153d6 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 18:44:19 +0100 Subject: [PATCH 018/130] Refactor Gateway module to implement traits and improve code structure --- examples/gateway_observers.rs | 3 +- examples/gateway_simple.rs | 3 +- src/gateway/gateway.rs | 295 +++++++++++++++++++--------------- src/gateway/handle.rs | 7 + src/gateway/heartbeat.rs | 33 +++- src/gateway/mod.rs | 288 +-------------------------------- tests/common/mod.rs | 6 +- tests/gateway.rs | 4 +- 8 files changed, 212 insertions(+), 427 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 0a54d31..562a366 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use chorus::gateway::GatewayCapable; use chorus::{ self, gateway::{Gateway, Observer}, @@ -30,7 +31,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection - let gateway = Gateway::new(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 276d95f..4549a2c 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use chorus::gateway::GatewayCapable; use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; use tokio::time::sleep; @@ -10,7 +11,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::new(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 15215ac..0c6b095 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -8,124 +8,25 @@ use crate::types::{ pub type GatewayStore = Arc>>>>; -#[derive(Debug)] -pub struct Gateway { - events: Arc>, - heartbeat_handler: HeartbeatHandler, - websocket_send: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - websocket_receive: SplitStream>>, - kill_send: tokio::sync::broadcast::Sender<()>, - store: GatewayStore, - url: String, -} - +#[allow(clippy::type_complexity)] #[async_trait] -impl - GatewayCapable< - WebSocketStream>, - WebSocketStream>, - GatewayHandle, - > for Gateway +pub trait GatewayCapable +where + R: Stream, + S: Sink, + G: GatewayHandleCapable, + H: HeartbeatHandlerCapable + Send + Sync, { - #[allow(clippy::new_ret_no_self)] - async fn get_handle(websocket_url: String) -> Result { - let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") - { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - } - let (websocket_stream, _) = match connect_async_tls_with_config( - &websocket_url, - None, - false, - Some(Connector::Rustls( - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots) - .with_no_client_auth() - .into(), - )), - ) - .await - { - Ok(websocket_stream) => websocket_stream, - Err(e) => { - return Err(GatewayError::CannotConnect { - error: e.to_string(), - }) - } - }; - - let (websocket_send, mut websocket_receive) = websocket_stream.split(); - - let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); - - // Create a shared broadcast channel for killing all gateway tasks - let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); - - // Wait for the first hello and then spawn both tasks so we avoid nested tasks - // This automatically spawns the heartbeat task, but from the main thread - let msg = websocket_receive.next().await.unwrap().unwrap(); - let gateway_payload: types::GatewayReceivePayload = - serde_json::from_str(msg.to_text().unwrap()).unwrap(); - - if gateway_payload.op_code != GATEWAY_HELLO { - return Err(GatewayError::NonHelloOnInitiate { - opcode: gateway_payload.op_code, - }); - } - - info!("GW: Received Hello"); - - let gateway_hello: types::HelloData = - serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); - - let events = Events::default(); - let shared_events = Arc::new(Mutex::new(events)); - - let store = Arc::new(Mutex::new(HashMap::new())); - - let mut gateway = Gateway { - events: shared_events.clone(), - heartbeat_handler: HeartbeatHandler::new( - Duration::from_millis(gateway_hello.heartbeat_interval), - shared_websocket_send.clone(), - kill_send.subscribe(), - ), - websocket_send: shared_websocket_send.clone(), - websocket_receive, - kill_send: kill_send.clone(), - store: store.clone(), - url: websocket_url.clone(), - }; - - // Now we can continuously check for messages in a different task, since we aren't going to receive another hello - task::spawn(async move { - gateway.gateway_listen_task().await; - }); - - Ok(GatewayHandle { - url: websocket_url.clone(), - events: shared_events, - websocket_send: shared_websocket_send.clone(), - kill_send: kill_send.clone(), - store, - }) - } - - /// Closes the websocket connection and stops all tasks - async fn close(&mut self) { - self.kill_send.send(()).unwrap(); - self.websocket_send.lock().await.close().await.unwrap(); - } - + fn get_events(&self) -> Arc>; + fn get_websocket_send(&self) -> Arc>>; + fn get_store(&self) -> GatewayStore; + fn get_url(&self) -> String; + fn get_heartbeat_handler(&self) -> &H; + /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] + /// + /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation + async fn get_handle(websocket_url: String) -> Result; + async fn close(&mut self); /// This handles a message as a websocket event and updates its events along with the events' observers async fn handle_message(&mut self, msg: GatewayMessage) { if msg.is_empty() { @@ -147,12 +48,16 @@ impl self.close().await; - self.events.lock().await.error.notify(error).await; + let events = self.get_events(); + let events = events.lock().await; + + events.error.notify(error).await; return; } let gateway_payload = msg.payload().unwrap(); + println!("gateway payload: {:#?}", &gateway_payload); // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes match gateway_payload.op_code { @@ -169,14 +74,16 @@ impl ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { match event_name.as_str() { $($name => { - let event = &mut self.events.lock().await.$($path).+; + let events = self.get_events(); + let event = &mut events.lock().await.$($path).+; let json = gateway_payload.event_data.unwrap().get(); match serde_json::from_str(json) { Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), Ok(message) => { $( let mut message: $message_type = message; - let store = self.store.lock().await; + let store = self.get_store(); + let store = store.lock().await; let id = if message.id().is_some() { message.id().unwrap() } else { @@ -195,7 +102,7 @@ impl let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; drop(inner_object); message.set_json(json.to_string()); - message.set_source_url(self.url.clone()); + message.set_source_url(self.get_url().clone()); message.update(downcasted.clone()); } else { warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) @@ -220,7 +127,9 @@ impl return; } Ok(sessions) => { - self.events.lock().await.session.replace.notify( + let events = self.get_events(); + let events = events.lock().await; + events.session.replace.notify( types::SessionsReplace {sessions} ).await; } @@ -320,8 +229,9 @@ impl op_code: Some(GATEWAY_HEARTBEAT), }; - self.heartbeat_handler - .send + let heartbeat_thread_communicator = self.get_heartbeat_handler().get_send(); + + heartbeat_thread_communicator .send(heartbeat_communication) .await .unwrap(); @@ -347,8 +257,10 @@ impl op_code: Some(GATEWAY_HEARTBEAT_ACK), }; - self.heartbeat_handler - .send + let heartbeat_handler = self.get_heartbeat_handler(); + let heartbeat_thread_communicator = heartbeat_handler.get_send(); + + heartbeat_thread_communicator .send(heartbeat_communication) .await .unwrap(); @@ -378,13 +290,138 @@ impl op_code: None, }; - self.heartbeat_handler - .send + let heartbeat_handler = self.get_heartbeat_handler(); + let heartbeat_thread_communicator = heartbeat_handler.get_send(); + heartbeat_thread_communicator .send(heartbeat_communication) .await .unwrap(); } } +} + +#[derive(Debug)] +pub struct Gateway { + events: Arc>, + heartbeat_handler: HeartbeatHandler, + websocket_send: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + websocket_receive: SplitStream>>, + kill_send: tokio::sync::broadcast::Sender<()>, + store: GatewayStore, + url: String, +} + +#[async_trait] +impl + GatewayCapable< + WebSocketStream>, + WebSocketStream>, + GatewayHandle, + HeartbeatHandler, + > for Gateway +{ + fn get_heartbeat_handler(&self) -> &HeartbeatHandler { + &self.heartbeat_handler + } + + #[allow(clippy::new_ret_no_self)] + async fn get_handle(websocket_url: String) -> Result { + let mut roots = rustls::RootCertStore::empty(); + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") + { + roots.add(&rustls::Certificate(cert.0)).unwrap(); + } + let (websocket_stream, _) = match connect_async_tls_with_config( + &websocket_url, + None, + false, + Some(Connector::Rustls( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() + .into(), + )), + ) + .await + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(GatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + let (websocket_send, mut websocket_receive) = websocket_stream.split(); + + let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); + + // Create a shared broadcast channel for killing all gateway tasks + let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); + + // Wait for the first hello and then spawn both tasks so we avoid nested tasks + // This automatically spawns the heartbeat task, but from the main thread + let msg = websocket_receive.next().await.unwrap().unwrap(); + let gateway_payload: types::GatewayReceivePayload = + serde_json::from_str(msg.to_text().unwrap()).unwrap(); + + if gateway_payload.op_code != GATEWAY_HELLO { + return Err(GatewayError::NonHelloOnInitiate { + opcode: gateway_payload.op_code, + }); + } + + info!("GW: Received Hello"); + + let gateway_hello: types::HelloData = + serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); + + let events = Events::default(); + let shared_events = Arc::new(Mutex::new(events)); + + let store = Arc::new(Mutex::new(HashMap::new())); + + let mut gateway = Gateway { + events: shared_events.clone(), + heartbeat_handler: HeartbeatHandler::new( + Duration::from_millis(gateway_hello.heartbeat_interval), + shared_websocket_send.clone(), + kill_send.subscribe(), + ), + websocket_send: shared_websocket_send.clone(), + websocket_receive, + kill_send: kill_send.clone(), + store: store.clone(), + url: websocket_url.clone(), + }; + + // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + task::spawn(async move { + gateway.gateway_listen_task().await; + }); + + Ok(GatewayHandle { + url: websocket_url.clone(), + events: shared_events, + websocket_send: shared_websocket_send.clone(), + kill_send: kill_send.clone(), + store, + }) + } + + /// Closes the websocket connection and stops all tasks + async fn close(&mut self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } fn get_events(&self) -> Arc> { self.events.clone() @@ -415,7 +452,9 @@ impl Gateway { // This if chain can be much better but if let is unstable on stable rust if let Some(Ok(message)) = msg { - self.handle_message(GatewayMessage::from_tungstenite_message(message)); + let _ = self + .handle_message(GatewayMessage::from_tungstenite_message(message)) + .await; continue; } diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 3a87c5a..8a18a5d 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -1,6 +1,13 @@ use super::{event::Events, *}; use crate::types::{self, Composite}; +pub trait GatewayHandleCapable +where + R: Stream, + S: Sink, +{ +} + /// Represents a handle to a Gateway connection. A Gateway connection will create observable /// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently /// implemented types with the trait [`WebSocketEvent`] diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index dd162b7..d0c43b3 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -5,10 +5,21 @@ use super::*; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; +pub trait HeartbeatHandlerCapable> { + fn new( + heartbeat_interval: Duration, + websocket_tx: Arc>>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> Self; + + fn get_send(&self) -> &Sender; + fn get_heartbeat_interval(&self) -> Duration; +} + /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used #[derive(Debug)] -pub(super) struct HeartbeatHandler { +pub struct HeartbeatHandler { /// How ofter heartbeats need to be sent at a minimum pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread @@ -17,8 +28,8 @@ pub(super) struct HeartbeatHandler { handle: JoinHandle<()>, } -impl HeartbeatHandler { - pub fn new( +impl HeartbeatHandlerCapable>> for HeartbeatHandler { + fn new( heartbeat_interval: Duration, websocket_tx: Arc< Mutex< @@ -50,6 +61,16 @@ impl HeartbeatHandler { } } + fn get_send(&self) -> &Sender { + &self.send + } + + fn get_heartbeat_interval(&self) -> Duration { + self.heartbeat_interval + } +} + +impl HeartbeatHandler { /// The main heartbeat task; /// /// Can be killed by the kill broadcast; @@ -141,9 +162,9 @@ impl HeartbeatHandler { /// Used for communications between the heartbeat and gateway thread. /// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server #[derive(Clone, Copy, Debug)] -pub(super) struct HeartbeatThreadCommunication { +pub struct HeartbeatThreadCommunication { /// The opcode for the communication we received, if relevant - pub(super) op_code: Option, + pub op_code: Option, /// The sequence number we got from discord, if any - pub(super) sequence_number: Option, + pub sequence_number: Option, } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 2b5d414..b5888b4 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -10,7 +10,7 @@ pub use message::*; use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; -use crate::types::{self, Snowflake, WebSocketEvent}; +use crate::types::{Snowflake, WebSocketEvent}; use async_trait::async_trait; use std::any::Any; @@ -35,8 +35,6 @@ use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; -use self::event::Events; - // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] const GATEWAY_DISPATCH: u8 = 0; @@ -138,290 +136,6 @@ impl GatewayEvent { } } -#[allow(clippy::type_complexity)] -#[async_trait] -pub trait GatewayCapable -where - R: Stream, - S: Sink, - G: GatewayHandleCapable, -{ - fn get_events(&self) -> Arc>; - fn get_websocket_send(&self) -> Arc>>; - fn get_store(&self) -> GatewayStore; - fn get_url(&self) -> String; - /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] - /// - /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - async fn get_handle(websocket_url: String) -> Result; - async fn close(&mut self); - async fn handle_message(&mut self, msg: GatewayMessage) { - if msg.is_empty() { - return; - } - - if !msg.is_error() && !msg.is_payload() { - warn!( - "Message unrecognised: {:?}, please open an issue on the chorus github", - msg.message.to_string() - ); - return; - } - - if msg.is_error() { - let error = msg.error().unwrap(); - - warn!("GW: Received error {:?}, connection will close..", error); - - self.close().await; - - self.get_events().lock().await.error.notify(error).await; - - return; - } - - let gateway_payload = msg.payload().unwrap(); - - // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes - match gateway_payload.op_code { - // An event was dispatched, we need to look at the gateway event name t - GATEWAY_DISPATCH => { - let Some(event_name) = gateway_payload.event_name else { - warn!("Gateway dispatch op without event_name"); - return; - }; - - trace!("Gateway: Received {event_name}"); - - macro_rules! handle { - ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { - match event_name.as_str() { - $($name => { - let event = &mut self.get_events().lock().await.$($path).+; - let json = gateway_payload.event_data.unwrap().get(); - match serde_json::from_str(json) { - Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), - Ok(message) => { - $( - let mut message: $message_type = message; - let store = self.get_store().lock().await; - let id = if message.id().is_some() { - message.id().unwrap() - } else { - event.notify(message).await; - return; - }; - if let Some(to_update) = store.get(&id) { - let object = to_update.clone(); - let inner_object = object.read().unwrap(); - if let Some(_) = inner_object.downcast_ref::<$update_type>() { - let ptr = Arc::into_raw(object.clone()); - // SAFETY: - // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. - // - This operation doesn't read or write any shared data, and thus cannot cause a data race - // - The reference count is not being modified - let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; - drop(inner_object); - message.set_json(json.to_string()); - message.set_source_url(self.get_url().clone()); - message.update(downcasted.clone()); - } else { - warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) - } - } - )? - event.notify(message).await; - } - } - },)* - "RESUMED" => (), - "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); - match result { - Err(err) => { - warn!( - "Failed to parse gateway event {} ({})", - event_name, - err - ); - return; - } - Ok(sessions) => { - self.events.lock().await.session.replace.notify( - types::SessionsReplace {sessions} - ).await; - } - } - }, - _ => { - warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); - } - } - }; - } - - // See https://discord.com/developers/docs/topics/gateway-events#receive-events - // "Some" of these are undocumented - handle!( - "READY" => session.ready, - "READY_SUPPLEMENTAL" => session.ready_supplemental, - "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, - "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, - "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule, - "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, - "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, - "CHANNEL_CREATE" => channel.create ChannelCreate: Guild, - "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, - "CHANNEL_UNREAD_UPDATE" => channel.unread_update, - "CHANNEL_DELETE" => channel.delete ChannelDelete: Guild, - "CHANNEL_PINS_UPDATE" => channel.pins_update, - "CALL_CREATE" => call.create, - "CALL_UPDATE" => call.update, - "CALL_DELETE" => call.delete, - "THREAD_CREATE" => thread.create, // TODO - "THREAD_UPDATE" => thread.update ThreadUpdate: Channel, - "THREAD_DELETE" => thread.delete, // TODO - "THREAD_LIST_SYNC" => thread.list_sync, // TODO - "THREAD_MEMBER_UPDATE" => thread.member_update, // TODO - "THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO - "GUILD_CREATE" => guild.create, // TODO - "GUILD_UPDATE" => guild.update, // TODO - "GUILD_DELETE" => guild.delete, // TODO - "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, - "GUILD_BAN_ADD" => guild.ban_add, // TODO - "GUILD_BAN_REMOVE" => guild.ban_remove, // TODO - "GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO - "GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO - "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, - "GUILD_MEMBER_ADD" => guild.member_add, - "GUILD_MEMBER_REMOVE" => guild.member_remove, - "GUILD_MEMBER_UPDATE" => guild.member_update, // TODO - "GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO - "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild, - "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject, - "GUILD_ROLE_DELETE" => guild.role_delete, // TODO - "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO - "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO - "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO - "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, - "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, - "PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO - "INTEGRATION_CREATE" => integration.create, // TODO - "INTEGRATION_UPDATE" => integration.update, // TODO - "INTEGRATION_DELETE" => integration.delete, // TODO - "INTERACTION_CREATE" => interaction.create, // TODO - "INVITE_CREATE" => invite.create, // TODO - "INVITE_DELETE" => invite.delete, // TODO - "MESSAGE_CREATE" => message.create, - "MESSAGE_UPDATE" => message.update, // TODO - "MESSAGE_DELETE" => message.delete, - "MESSAGE_DELETE_BULK" => message.delete_bulk, - "MESSAGE_REACTION_ADD" => message.reaction_add, // TODO - "MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO - "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO - "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO - "MESSAGE_ACK" => message.ack, - "PRESENCE_UPDATE" => user.presence_update, // TODO - "RELATIONSHIP_ADD" => relationship.add, - "RELATIONSHIP_REMOVE" => relationship.remove, - "STAGE_INSTANCE_CREATE" => stage_instance.create, - "STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO - "STAGE_INSTANCE_DELETE" => stage_instance.delete, - "TYPING_START" => user.typing_start, - "USER_UPDATE" => user.update, // TODO - "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, - "VOICE_STATE_UPDATE" => voice.state_update, // TODO - "VOICE_SERVER_UPDATE" => voice.server_update, - "WEBHOOKS_UPDATE" => webhooks.update - ); - } - // We received a heartbeat from the server - // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." - GATEWAY_HEARTBEAT => { - trace!("GW: Received Heartbeat // Heartbeat Request"); - - // Tell the heartbeat handler it should send a heartbeat right away - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT), - }; - - self.heartbeat_handler - .send - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_RECONNECT => { - todo!() - } - GATEWAY_INVALID_SESSION => { - todo!() - } - // Starts our heartbeat - // We should have already handled this in gateway init - GATEWAY_HELLO => { - warn!("Received hello when it was unexpected"); - } - GATEWAY_HEARTBEAT_ACK => { - trace!("GW: Received Heartbeat ACK"); - - // Tell the heartbeat handler we received an ack - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT_ACK), - }; - - self.heartbeat_handler - .send - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_IDENTIFY - | GATEWAY_UPDATE_PRESENCE - | GATEWAY_UPDATE_VOICE_STATE - | GATEWAY_RESUME - | GATEWAY_REQUEST_GUILD_MEMBERS - | GATEWAY_CALL_SYNC - | GATEWAY_LAZY_REQUEST => { - info!( - "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", - gateway_payload.op_code - ); - } - _ => { - warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); - } - } - - // If we we received a seq number we should let it know - if let Some(seq_num) = gateway_payload.sequence_number { - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: Some(seq_num), - // Op code is irrelevant here - op_code: None, - }; - - self.heartbeat_handler - .send - .send(heartbeat_communication) - .await - .unwrap(); - } - } -} - -pub trait GatewayHandleCapable -where - R: Stream, - S: Sink, -{ -} - #[cfg(test)] mod test { use crate::types; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index ce42578..7c9d652 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, GatewayCapable}; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -43,7 +43,9 @@ impl TestBundle { limits: self.user.limits.clone(), settings: self.user.settings.clone(), object: self.user.object.clone(), - gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(), + gateway: Gateway::get_handle(self.instance.urls.wss.clone()) + .await + .unwrap(), } } } diff --git a/tests/gateway.rs b/tests/gateway.rs index 991c9f2..eb2dbc3 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -10,7 +10,7 @@ use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObjec async fn test_gateway_establish() { let bundle = common::setup().await; - Gateway::new(bundle.urls.wss.clone()).await.unwrap(); + Gateway::get_handle(bundle.urls.wss.clone()).await.unwrap(); common::teardown(bundle).await } @@ -19,7 +19,7 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap(); + let gateway = Gateway::get_handle(bundle.urls.wss.clone()).await.unwrap(); let mut identify = types::GatewayIdentifyPayload::common(); identify.token = bundle.user.token.clone(); From 20c9066e6f71c436c94be47998078d04a0fa3a20 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:18:50 +0100 Subject: [PATCH 019/130] Properly extract all extractable methods from GatewayHandle into Trait --- examples/gateway_observers.rs | 2 +- examples/gateway_simple.rs | 2 +- src/api/auth/login.rs | 2 +- src/api/auth/mod.rs | 2 +- src/api/auth/register.rs | 2 +- src/gateway/handle.rs | 212 +++++++++++++++++++--------------- 6 files changed, 122 insertions(+), 100 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 562a366..d0ba9dd 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use chorus::gateway::GatewayCapable; +use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; use chorus::{ self, gateway::{Gateway, Observer}, diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 4549a2c..b522b47 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use chorus::gateway::GatewayCapable; +use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; use tokio::time::sleep; diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 750d01e..027056a 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -4,7 +4,7 @@ use reqwest::Client; use serde_json::to_string; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayCapable}; +use crate::gateway::{Gateway, GatewayCapable, GatewayHandleCapable}; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 091975c..480c771 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; pub use login::*; pub use register::*; -use crate::gateway::GatewayCapable; +use crate::gateway::{GatewayCapable, GatewayHandleCapable}; use crate::{ errors::ChorusResult, gateway::Gateway, diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index ed6fcbf..fa5c59e 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; use reqwest::Client; use serde_json::to_string; -use crate::gateway::{Gateway, GatewayCapable, GatewayHandle}; +use crate::gateway::{Gateway, GatewayCapable, GatewayHandle, GatewayHandleCapable}; use crate::types::GatewayIdentifyPayload; use crate::{ errors::ChorusResult, diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 8a18a5d..b5d748e 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -1,11 +1,128 @@ use super::{event::Events, *}; use crate::types::{self, Composite}; +#[async_trait(?Send)] pub trait GatewayHandleCapable where R: Stream, S: Sink, { + /// Sends json to the gateway with an opcode + async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value); + + /// Observes an Item ``, which will update itself, if new information about this + /// item arrives on the corresponding Gateway Thread + async fn observe + Send + Sync>( + &self, + object: Arc>, + ) -> Arc>; + + /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` + /// with all of its observable fields being observed. + async fn observe_and_into_inner>( + &self, + object: Arc>, + ) -> T { + let channel = self.observe(object.clone()).await; + let object = channel.read().unwrap().clone(); + object + } + + /// Sends an identify event to the gateway + async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Identify.."); + + self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; + } + + /// Sends an update presence event to the gateway + async fn send_update_presence(&self, to_send: types::UpdatePresence) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Update Presence.."); + + self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) + .await; + } + + /// Sends a resume event to the gateway + async fn send_resume(&self, to_send: types::GatewayResume) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Resume.."); + + self.send_json_event(GATEWAY_RESUME, to_send_value).await; + } + + /// Sends a request guild members to the server + async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Request Guild Members.."); + + self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) + .await; + } + + /// Sends an update voice state to the server + async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { + let to_send_value = serde_json::to_value(to_send).unwrap(); + + trace!("GW: Sending Update Voice State.."); + + self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) + .await; + } + + /// Sends a call sync to the server + async fn send_call_sync(&self, to_send: types::CallSync) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Call Sync.."); + + self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; + } + + /// Sends a Lazy Request + async fn send_lazy_request(&self, to_send: types::LazyRequest) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Lazy Request.."); + + self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) + .await; + } + + /// Closes the websocket connection and stops all gateway tasks; + /// + /// Esentially pulls the plug on the gateway, leaving it possible to resume; + async fn close(&self); +} + +#[async_trait(?Send)] +impl + GatewayHandleCapable< + WebSocketStream>, + WebSocketStream>, + > for GatewayHandle +{ + async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { + self.send_json_event(op_code, to_send).await + } + + async fn observe>( + &self, + object: Arc>, + ) -> Arc> { + self.observe(object).await + } + + async fn close(&self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } } /// Represents a handle to a Gateway connection. A Gateway connection will create observable @@ -30,7 +147,6 @@ pub struct GatewayHandle { } impl GatewayHandle { - /// Sends json to the gateway with an opcode async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { let gateway_payload = types::GatewaySendPayload { op_code, @@ -89,98 +205,4 @@ impl GatewayHandle { wrapped } } - - /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` - /// with all of its observable fields being observed. - pub async fn observe_and_into_inner>( - &self, - object: Arc>, - ) -> T { - let channel = self.observe(object.clone()).await; - let object = channel.read().unwrap().clone(); - object - } - - /// Sends an identify event to the gateway - pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Identify.."); - - self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; - } - - /// Sends a resume event to the gateway - pub async fn send_resume(&self, to_send: types::GatewayResume) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Resume.."); - - self.send_json_event(GATEWAY_RESUME, to_send_value).await; - } - - /// Sends an update presence event to the gateway - pub async fn send_update_presence(&self, to_send: types::UpdatePresence) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Update Presence.."); - - self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) - .await; - } - - /// Sends a request guild members to the server - pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Request Guild Members.."); - - self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) - .await; - } - - /// Sends an update voice state to the server - pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { - let to_send_value = serde_json::to_value(to_send).unwrap(); - - trace!("GW: Sending Update Voice State.."); - - self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) - .await; - } - - /// Sends a call sync to the server - pub async fn send_call_sync(&self, to_send: types::CallSync) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Call Sync.."); - - self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; - } - - /// Sends a Lazy Request - pub async fn send_lazy_request(&self, to_send: types::LazyRequest) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Lazy Request.."); - - self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) - .await; - } - - /// Closes the websocket connection and stops all gateway tasks; - /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; - pub async fn close(&self) { - self.kill_send.send(()).unwrap(); - self.websocket_send.lock().await.close().await.unwrap(); - } -} - -impl - GatewayHandleCapable< - WebSocketStream>, - WebSocketStream>, - > for GatewayHandle -{ } From 9ac36ab8e893f3fc8856977ae4cfbc57c74f17da Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:26:47 +0100 Subject: [PATCH 020/130] Fix clippy warning --- src/instance.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 8dc9e2b..ada99a6 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -36,14 +36,11 @@ impl Instance { pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { let limits_information; if limited { - let limits_configuration = - Some(ChorusRequest::get_limits_config(&urls.api).await?.rate); - let limits = Some(ChorusRequest::limits_config_to_hashmap( - limits_configuration.as_ref().unwrap(), - )); + let limits_configuration = ChorusRequest::get_limits_config(&urls.api).await?.rate; + let limits = ChorusRequest::limits_config_to_hashmap(&limits_configuration); limits_information = Some(LimitsInformation { - ratelimits: limits.unwrap(), - configuration: limits_configuration.unwrap(), + ratelimits: limits, + configuration: limits_configuration, }); } else { limits_information = None; From e4cc201b790dcb1b601ec341ac5a57047fbb932b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:56:58 +0100 Subject: [PATCH 021/130] Remove all imports of Gateway and GatewayHandle --- examples/gateway_observers.rs | 2 +- examples/gateway_simple.rs | 2 +- src/api/auth/login.rs | 2 +- src/api/auth/register.rs | 2 +- src/instance.rs | 4 ++-- src/lib.rs | 5 +++++ src/types/entities/channel.rs | 2 +- src/types/entities/emoji.rs | 2 +- src/types/entities/guild.rs | 2 +- src/types/entities/mod.rs | 2 +- src/types/entities/role.rs | 2 +- src/types/entities/user.rs | 2 +- src/types/entities/voice_state.rs | 2 +- src/types/entities/webhook.rs | 2 +- 14 files changed, 19 insertions(+), 14 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index d0ba9dd..acb8df9 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; use chorus::{ self, - gateway::{Gateway, Observer}, + gateway::Observer, types::{GatewayIdentifyPayload, GatewayReady}, }; use std::{sync::Arc, time::Duration}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index b522b47..dbc26a4 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,7 +1,7 @@ use std::time::Duration; use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; -use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; +use chorus::{self, types::GatewayIdentifyPayload}; use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 027056a..208e94d 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -4,7 +4,7 @@ use reqwest::Client; use serde_json::to_string; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayCapable, GatewayHandleCapable}; +use crate::gateway::{GatewayCapable, GatewayHandleCapable}; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index fa5c59e..79a5bcc 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; use reqwest::Client; use serde_json::to_string; -use crate::gateway::{Gateway, GatewayCapable, GatewayHandle, GatewayHandleCapable}; +use crate::gateway::{GatewayCapable, GatewayHandleCapable}; use crate::types::GatewayIdentifyPayload; use crate::{ errors::ChorusResult, diff --git a/src/instance.rs b/src/instance.rs index ada99a6..4acfd26 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -9,11 +9,11 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayCapable, GatewayHandle}; +use crate::gateway::GatewayCapable; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, Limit, LimitType, User, UserSettings}; -use crate::UrlBundle; +use crate::{Gateway, GatewayHandle, UrlBundle}; #[derive(Debug, Clone, Default)] /// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server. diff --git a/src/lib.rs b/src/lib.rs index 47bbaab..e7fab0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,11 @@ #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); +pub type Gateway = WebsocketGateway; +pub type GatewayHandle = WebsocketGatewayHandle; + +use gateway::Gateway as WebsocketGateway; +use gateway::GatewayHandle as WebsocketGatewayHandle; use url::{ParseError, Url}; #[cfg(feature = "client")] diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 280401c..66768bd 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -15,7 +15,7 @@ use crate::types::{ use crate::types::Composite; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{observe_option_vec, Composite, Updateable}; diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index 4f56af5..301b0be 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -10,7 +10,7 @@ use crate::types::Snowflake; use crate::types::Composite; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{Composite, Updateable}; diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index bb4db0c..a3c2182 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -16,7 +16,7 @@ use bitflags::bitflags; use super::PublicUser; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index a14ef2c..8d788c4 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -24,7 +24,7 @@ pub use voice_state::*; pub use webhook::*; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use async_trait::async_trait; diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 087a775..349af02 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -9,7 +9,7 @@ use crate::types::utils::Snowflake; use chorus_macros::{Composite, Updateable}; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use crate::types::Composite; diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index eca5344..016a617 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -5,7 +5,7 @@ use serde_aux::prelude::deserialize_option_number_from_string; use std::fmt::Debug; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use crate::types::Composite; diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 1c36268..569bbc7 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -7,7 +7,7 @@ use chorus_macros::Composite; use crate::types::Composite; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index b544ec9..9560d89 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{Composite, Updateable}; From 5bbf1cef1f899f75499fcef489d4cc9e8f75d467 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:58:10 +0100 Subject: [PATCH 022/130] Add prefix "Default" To Gateway, -Handler and HeartbeatHandler --- src/api/auth/mod.rs | 6 ++++-- src/gateway/gateway.rs | 22 +++++++++++----------- src/gateway/handle.rs | 6 +++--- src/gateway/heartbeat.rs | 12 +++++++----- src/lib.rs | 4 ++-- tests/common/mod.rs | 4 ++-- tests/gateway.rs | 8 ++++++-- 7 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 480c771..402af5c 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -6,7 +6,7 @@ pub use register::*; use crate::gateway::{GatewayCapable, GatewayHandleCapable}; use crate::{ errors::ChorusResult, - gateway::Gateway, + gateway::DefaultGateway, instance::{ChorusUser, Instance}, types::{GatewayIdentifyPayload, User}, }; @@ -26,7 +26,9 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); + let gateway = DefaultGateway::get_handle(self.urls.wss.clone()) + .await + .unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 0c6b095..9f8ec38 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -301,9 +301,9 @@ where } #[derive(Debug)] -pub struct Gateway { +pub struct DefaultGateway { events: Arc>, - heartbeat_handler: HeartbeatHandler, + heartbeat_handler: DefaultHeartbeatHandler, websocket_send: Arc< Mutex< SplitSink< @@ -323,16 +323,16 @@ impl GatewayCapable< WebSocketStream>, WebSocketStream>, - GatewayHandle, - HeartbeatHandler, - > for Gateway + DefaultGatewayHandle, + DefaultHeartbeatHandler, + > for DefaultGateway { - fn get_heartbeat_handler(&self) -> &HeartbeatHandler { + fn get_heartbeat_handler(&self) -> &DefaultHeartbeatHandler { &self.heartbeat_handler } #[allow(clippy::new_ret_no_self)] - async fn get_handle(websocket_url: String) -> Result { + async fn get_handle(websocket_url: String) -> Result { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { @@ -389,9 +389,9 @@ impl let store = Arc::new(Mutex::new(HashMap::new())); - let mut gateway = Gateway { + let mut gateway = DefaultGateway { events: shared_events.clone(), - heartbeat_handler: HeartbeatHandler::new( + heartbeat_handler: DefaultHeartbeatHandler::new( Duration::from_millis(gateway_hello.heartbeat_interval), shared_websocket_send.clone(), kill_send.subscribe(), @@ -408,7 +408,7 @@ impl gateway.gateway_listen_task().await; }); - Ok(GatewayHandle { + Ok(DefaultGatewayHandle { url: websocket_url.clone(), events: shared_events, websocket_send: shared_websocket_send.clone(), @@ -442,7 +442,7 @@ impl } } -impl Gateway { +impl DefaultGateway { /// The main gateway listener task; /// /// Can only be stopped by closing the websocket, cannot be made to listen for kill diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index b5d748e..6d018a9 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -106,7 +106,7 @@ impl GatewayHandleCapable< WebSocketStream>, WebSocketStream>, - > for GatewayHandle + > for DefaultGatewayHandle { async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { self.send_json_event(op_code, to_send).await @@ -130,7 +130,7 @@ impl /// implemented types with the trait [`WebSocketEvent`] /// Using this handle you can also send Gateway Events directly. #[derive(Debug, Clone)] -pub struct GatewayHandle { +pub struct DefaultGatewayHandle { pub url: String, pub events: Arc>, pub websocket_send: Arc< @@ -146,7 +146,7 @@ pub struct GatewayHandle { pub(crate) store: GatewayStore, } -impl GatewayHandle { +impl DefaultGatewayHandle { async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { let gateway_payload = types::GatewaySendPayload { op_code, diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index d0c43b3..6906e33 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -19,7 +19,7 @@ pub trait HeartbeatHandlerCapable> { /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used #[derive(Debug)] -pub struct HeartbeatHandler { +pub struct DefaultHeartbeatHandler { /// How ofter heartbeats need to be sent at a minimum pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread @@ -28,7 +28,9 @@ pub struct HeartbeatHandler { handle: JoinHandle<()>, } -impl HeartbeatHandlerCapable>> for HeartbeatHandler { +impl HeartbeatHandlerCapable>> + for DefaultHeartbeatHandler +{ fn new( heartbeat_interval: Duration, websocket_tx: Arc< @@ -40,12 +42,12 @@ impl HeartbeatHandlerCapable>> for Hea >, >, kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { + ) -> DefaultHeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( + DefaultHeartbeatHandler::heartbeat_task( websocket_tx, heartbeat_interval, receive, @@ -70,7 +72,7 @@ impl HeartbeatHandlerCapable>> for Hea } } -impl HeartbeatHandler { +impl DefaultHeartbeatHandler { /// The main heartbeat task; /// /// Can be killed by the kill broadcast; diff --git a/src/lib.rs b/src/lib.rs index e7fab0e..b680124 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,8 @@ compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled pub type Gateway = WebsocketGateway; pub type GatewayHandle = WebsocketGatewayHandle; -use gateway::Gateway as WebsocketGateway; -use gateway::GatewayHandle as WebsocketGatewayHandle; +use gateway::DefaultGateway as WebsocketGateway; +use gateway::DefaultGatewayHandle as WebsocketGatewayHandle; use url::{ParseError, Url}; #[cfg(feature = "client")] diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7c9d652..95c2de3 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; -use chorus::gateway::{Gateway, GatewayCapable}; +use chorus::gateway::{DefaultGateway, GatewayCapable}; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -43,7 +43,7 @@ impl TestBundle { limits: self.user.limits.clone(), settings: self.user.settings.clone(), object: self.user.object.clone(), - gateway: Gateway::get_handle(self.instance.urls.wss.clone()) + gateway: DefaultGateway::get_handle(self.instance.urls.wss.clone()) .await .unwrap(), } diff --git a/tests/gateway.rs b/tests/gateway.rs index eb2dbc3..34c841f 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -10,7 +10,9 @@ use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObjec async fn test_gateway_establish() { let bundle = common::setup().await; - Gateway::get_handle(bundle.urls.wss.clone()).await.unwrap(); + DefaultGateway::get_handle(bundle.urls.wss.clone()) + .await + .unwrap(); common::teardown(bundle).await } @@ -19,7 +21,9 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = Gateway::get_handle(bundle.urls.wss.clone()).await.unwrap(); + let gateway = DefaultGateway::get_handle(bundle.urls.wss.clone()) + .await + .unwrap(); let mut identify = types::GatewayIdentifyPayload::common(); identify.token = bundle.user.token.clone(); From b93b3690da879c99527af203e41010b774209f5b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 21:03:43 +0100 Subject: [PATCH 023/130] Refactor project to use chorus::Gateway[...] --- examples/gateway_observers.rs | 1 + examples/gateway_simple.rs | 2 +- src/api/auth/login.rs | 1 + src/api/auth/register.rs | 1 + src/types/entities/channel.rs | 2 +- src/types/entities/emoji.rs | 2 +- src/types/entities/guild.rs | 4 ++-- src/types/entities/mod.rs | 2 +- src/types/entities/role.rs | 2 +- src/types/entities/user.rs | 2 +- src/types/entities/voice_state.rs | 2 +- src/types/entities/webhook.rs | 2 +- 12 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index acb8df9..511c568 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -4,6 +4,7 @@ use chorus::{ self, gateway::Observer, types::{GatewayIdentifyPayload, GatewayReady}, + Gateway, }; use std::{sync::Arc, time::Duration}; use tokio::{self, time::sleep}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index dbc26a4..e46d122 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,7 +1,7 @@ use std::time::Duration; use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; -use chorus::{self, types::GatewayIdentifyPayload}; +use chorus::{self, types::GatewayIdentifyPayload, Gateway}; use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 208e94d..71d594f 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -8,6 +8,7 @@ use crate::gateway::{GatewayCapable, GatewayHandleCapable}; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; +use crate::Gateway; impl Instance { /// Logs into an existing account on the spacebar server. diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 79a5bcc..f416124 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -12,6 +12,7 @@ use crate::{ types::LimitType, types::RegisterSchema, }; +use crate::{Gateway, GatewayHandle}; impl Instance { /// Registers a new user on the server. diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 66768bd..1d1c58c 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -12,7 +12,7 @@ use crate::types::{ }; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index 301b0be..8c0b8e6 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -7,7 +7,7 @@ use crate::types::entities::User; use crate::types::Snowflake; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index a3c2182..8638ff7 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::sync::{Arc, RwLock}; +use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -11,12 +12,11 @@ use crate::types::{ interfaces::WelcomeScreenObject, utils::Snowflake, }; -use bitflags::bitflags; use super::PublicUser; #[cfg(feature = "client")] -use crate::gateway::Updateable; +use crate::{gateway::Updateable, GatewayHandle}; #[cfg(feature = "client")] use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 8d788c4..3371598 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -24,7 +24,7 @@ pub use voice_state::*; pub use webhook::*; #[cfg(feature = "client")] -use crate::gateway::Updateable; +use crate::{gateway::Updateable, GatewayHandle}; #[cfg(feature = "client")] use async_trait::async_trait; diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 349af02..1166431 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -12,7 +12,7 @@ use chorus_macros::{Composite, Updateable}; use crate::gateway::Updateable; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index 016a617..e247240 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; use crate::gateway::Updateable; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; #[cfg(feature = "client")] use chorus_macros::{Composite, Updateable}; diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 569bbc7..3f122d6 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, RwLock}; use chorus_macros::Composite; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index 9560d89..3d8c687 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -10,7 +10,7 @@ use crate::gateway::Updateable; use chorus_macros::{Composite, Updateable}; #[cfg(feature = "client")] -use crate::types::Composite; +use crate::{types::Composite, GatewayHandle}; use crate::types::{ entities::{Guild, User}, From 10eafe46ccdb09343753a9627ed112adc4a3e932 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 21:09:24 +0100 Subject: [PATCH 024/130] Feature lock DefaultGateway+Handle for non-wasm32 --- src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b680124..7c1927f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,13 @@ #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); -pub type Gateway = WebsocketGateway; -pub type GatewayHandle = WebsocketGatewayHandle; +#[cfg(not(target_arch = "wasm32"))] +pub type Gateway = DefaultGateway; +#[cfg(not(target_arch = "wasm32"))] +pub type GatewayHandle = DefaultGatewayHandle; -use gateway::DefaultGateway as WebsocketGateway; -use gateway::DefaultGatewayHandle as WebsocketGatewayHandle; +use gateway::DefaultGateway; +use gateway::DefaultGatewayHandle; use url::{ParseError, Url}; #[cfg(feature = "client")] From 5ae0521e8ef880c3b7fe6247484754c0e8a56b38 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:26:15 +0100 Subject: [PATCH 025/130] re-organize files --- src/gateway/default/gateway.rs | 349 +++++++++++++ src/gateway/{ => default}/handle.rs | 100 ---- src/gateway/{ => default}/heartbeat.rs | 11 - src/gateway/{ => default}/message.rs | 2 +- src/gateway/default/mod.rs | 84 ++++ src/gateway/gateway.rs | 645 ------------------------- src/gateway/mod.rs | 472 +++++++++++++++--- 7 files changed, 842 insertions(+), 821 deletions(-) create mode 100644 src/gateway/default/gateway.rs rename src/gateway/{ => default}/handle.rs (52%) rename src/gateway/{ => default}/heartbeat.rs (94%) rename src/gateway/{ => default}/message.rs (97%) create mode 100644 src/gateway/default/mod.rs delete mode 100644 src/gateway/gateway.rs diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs new file mode 100644 index 0000000..a90a2e9 --- /dev/null +++ b/src/gateway/default/gateway.rs @@ -0,0 +1,349 @@ +use futures_util::StreamExt; + +use self::event::Events; +use super::*; +use crate::types::{self, WebSocketEvent}; + +#[derive(Debug)] +pub struct DefaultGateway { + events: Arc>, + heartbeat_handler: DefaultHeartbeatHandler, + websocket_send: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + websocket_receive: SplitStream>>, + kill_send: tokio::sync::broadcast::Sender<()>, + store: GatewayStore, + url: String, +} + +#[async_trait] +impl + GatewayCapable< + WebSocketStream>, + WebSocketStream>, + DefaultGatewayHandle, + DefaultHeartbeatHandler, + > for DefaultGateway +{ + fn get_heartbeat_handler(&self) -> &DefaultHeartbeatHandler { + &self.heartbeat_handler + } + + #[allow(clippy::new_ret_no_self)] + async fn get_handle(websocket_url: String) -> Result { + let mut roots = rustls::RootCertStore::empty(); + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") + { + roots.add(&rustls::Certificate(cert.0)).unwrap(); + } + let (websocket_stream, _) = match connect_async_tls_with_config( + &websocket_url, + None, + false, + Some(Connector::Rustls( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() + .into(), + )), + ) + .await + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(GatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + let (websocket_send, mut websocket_receive) = websocket_stream.split(); + + let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); + + // Create a shared broadcast channel for killing all gateway tasks + let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); + + // Wait for the first hello and then spawn both tasks so we avoid nested tasks + // This automatically spawns the heartbeat task, but from the main thread + let msg = websocket_receive.next().await.unwrap().unwrap(); + let gateway_payload: types::GatewayReceivePayload = + serde_json::from_str(msg.to_text().unwrap()).unwrap(); + + if gateway_payload.op_code != GATEWAY_HELLO { + return Err(GatewayError::NonHelloOnInitiate { + opcode: gateway_payload.op_code, + }); + } + + info!("GW: Received Hello"); + + let gateway_hello: types::HelloData = + serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); + + let events = Events::default(); + let shared_events = Arc::new(Mutex::new(events)); + + let store = Arc::new(Mutex::new(HashMap::new())); + + let mut gateway = DefaultGateway { + events: shared_events.clone(), + heartbeat_handler: DefaultHeartbeatHandler::new( + Duration::from_millis(gateway_hello.heartbeat_interval), + shared_websocket_send.clone(), + kill_send.subscribe(), + ), + websocket_send: shared_websocket_send.clone(), + websocket_receive, + kill_send: kill_send.clone(), + store: store.clone(), + url: websocket_url.clone(), + }; + + // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + task::spawn(async move { + gateway.gateway_listen_task().await; + }); + + Ok(DefaultGatewayHandle { + url: websocket_url.clone(), + events: shared_events, + websocket_send: shared_websocket_send.clone(), + kill_send: kill_send.clone(), + store, + }) + } + + /// Closes the websocket connection and stops all tasks + async fn close(&mut self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } + + fn get_events(&self) -> Arc> { + self.events.clone() + } + + fn get_websocket_send( + &self, + ) -> Arc>, Message>>> { + self.websocket_send.clone() + } + + fn get_store(&self) -> GatewayStore { + self.store.clone() + } + + fn get_url(&self) -> String { + self.url.clone() + } +} + +impl DefaultGateway { + /// The main gateway listener task; + /// + /// Can only be stopped by closing the websocket, cannot be made to listen for kill + pub async fn gateway_listen_task(&mut self) { + loop { + let msg = self.websocket_receive.next().await; + + // This if chain can be much better but if let is unstable on stable rust + if let Some(Ok(message)) = msg { + let _ = self + .handle_message(GatewayMessage::from_tungstenite_message(message)) + .await; + continue; + } + + // We couldn't receive the next message or it was an error, something is wrong with the websocket, close + warn!("GW: Websocket is broken, stopping gateway"); + break; + } + } + + /// Deserializes and updates a dispatched event, when we already know its type; + /// (Called for every event in handle_message) + #[allow(dead_code)] // TODO: Remove this allow annotation + async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( + data: &'a str, + event: &mut GatewayEvent, + ) -> Result<(), serde_json::Error> { + let data_deserialize_result: Result = serde_json::from_str(data); + + if data_deserialize_result.is_err() { + return Err(data_deserialize_result.err().unwrap()); + } + + event.notify(data_deserialize_result.unwrap()).await; + Ok(()) + } +} + +pub mod event { + use super::*; + + #[derive(Default, Debug)] + pub struct Events { + pub application: Application, + pub auto_moderation: AutoModeration, + pub session: Session, + pub message: Message, + pub user: User, + pub relationship: Relationship, + pub channel: Channel, + pub thread: Thread, + pub guild: Guild, + pub invite: Invite, + pub integration: Integration, + pub interaction: Interaction, + pub stage_instance: StageInstance, + pub call: Call, + pub voice: Voice, + pub webhooks: Webhooks, + pub gateway_identify_payload: GatewayEvent, + pub gateway_resume: GatewayEvent, + pub error: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Application { + pub command_permissions_update: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct AutoModeration { + pub rule_create: GatewayEvent, + pub rule_update: GatewayEvent, + pub rule_delete: GatewayEvent, + pub action_execution: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Session { + pub ready: GatewayEvent, + pub ready_supplemental: GatewayEvent, + pub replace: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct StageInstance { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Message { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub delete_bulk: GatewayEvent, + pub reaction_add: GatewayEvent, + pub reaction_remove: GatewayEvent, + pub reaction_remove_all: GatewayEvent, + pub reaction_remove_emoji: GatewayEvent, + pub ack: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct User { + pub update: GatewayEvent, + pub guild_settings_update: GatewayEvent, + pub presence_update: GatewayEvent, + pub typing_start: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Relationship { + pub add: GatewayEvent, + pub remove: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Channel { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub unread_update: GatewayEvent, + pub delete: GatewayEvent, + pub pins_update: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Thread { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub list_sync: GatewayEvent, + pub member_update: GatewayEvent, + pub members_update: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Guild { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub audit_log_entry_create: GatewayEvent, + pub ban_add: GatewayEvent, + pub ban_remove: GatewayEvent, + pub emojis_update: GatewayEvent, + pub stickers_update: GatewayEvent, + pub integrations_update: GatewayEvent, + pub member_add: GatewayEvent, + pub member_remove: GatewayEvent, + pub member_update: GatewayEvent, + pub members_chunk: GatewayEvent, + pub role_create: GatewayEvent, + pub role_update: GatewayEvent, + pub role_delete: GatewayEvent, + pub role_scheduled_event_create: GatewayEvent, + pub role_scheduled_event_update: GatewayEvent, + pub role_scheduled_event_delete: GatewayEvent, + pub role_scheduled_event_user_add: GatewayEvent, + pub role_scheduled_event_user_remove: GatewayEvent, + pub passive_update_v1: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Invite { + pub create: GatewayEvent, + pub delete: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Integration { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Interaction { + pub create: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Call { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Voice { + pub state_update: GatewayEvent, + pub server_update: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Webhooks { + pub update: GatewayEvent, + } +} diff --git a/src/gateway/handle.rs b/src/gateway/default/handle.rs similarity index 52% rename from src/gateway/handle.rs rename to src/gateway/default/handle.rs index 6d018a9..14eb9cb 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/default/handle.rs @@ -1,106 +1,6 @@ use super::{event::Events, *}; use crate::types::{self, Composite}; -#[async_trait(?Send)] -pub trait GatewayHandleCapable -where - R: Stream, - S: Sink, -{ - /// Sends json to the gateway with an opcode - async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value); - - /// Observes an Item ``, which will update itself, if new information about this - /// item arrives on the corresponding Gateway Thread - async fn observe + Send + Sync>( - &self, - object: Arc>, - ) -> Arc>; - - /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` - /// with all of its observable fields being observed. - async fn observe_and_into_inner>( - &self, - object: Arc>, - ) -> T { - let channel = self.observe(object.clone()).await; - let object = channel.read().unwrap().clone(); - object - } - - /// Sends an identify event to the gateway - async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Identify.."); - - self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; - } - - /// Sends an update presence event to the gateway - async fn send_update_presence(&self, to_send: types::UpdatePresence) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Update Presence.."); - - self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) - .await; - } - - /// Sends a resume event to the gateway - async fn send_resume(&self, to_send: types::GatewayResume) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Resume.."); - - self.send_json_event(GATEWAY_RESUME, to_send_value).await; - } - - /// Sends a request guild members to the server - async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Request Guild Members.."); - - self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) - .await; - } - - /// Sends an update voice state to the server - async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { - let to_send_value = serde_json::to_value(to_send).unwrap(); - - trace!("GW: Sending Update Voice State.."); - - self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) - .await; - } - - /// Sends a call sync to the server - async fn send_call_sync(&self, to_send: types::CallSync) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Call Sync.."); - - self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; - } - - /// Sends a Lazy Request - async fn send_lazy_request(&self, to_send: types::LazyRequest) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Lazy Request.."); - - self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) - .await; - } - - /// Closes the websocket connection and stops all gateway tasks; - /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; - async fn close(&self); -} - #[async_trait(?Send)] impl GatewayHandleCapable< diff --git a/src/gateway/heartbeat.rs b/src/gateway/default/heartbeat.rs similarity index 94% rename from src/gateway/heartbeat.rs rename to src/gateway/default/heartbeat.rs index 6906e33..b737366 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -5,17 +5,6 @@ use super::*; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; -pub trait HeartbeatHandlerCapable> { - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc>>, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> Self; - - fn get_send(&self) -> &Sender; - fn get_heartbeat_interval(&self) -> Duration; -} - /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used #[derive(Debug)] diff --git a/src/gateway/message.rs b/src/gateway/default/message.rs similarity index 97% rename from src/gateway/message.rs rename to src/gateway/default/message.rs index edee9dd..12d0e1a 100644 --- a/src/gateway/message.rs +++ b/src/gateway/default/message.rs @@ -7,7 +7,7 @@ use super::*; #[derive(Clone, Debug)] pub struct GatewayMessage { /// The message we received from the server - pub(super) message: tokio_tungstenite::tungstenite::Message, + pub(crate) message: tokio_tungstenite::tungstenite::Message, } impl GatewayMessage { diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs new file mode 100644 index 0000000..4843b96 --- /dev/null +++ b/src/gateway/default/mod.rs @@ -0,0 +1,84 @@ +pub mod gateway; +pub mod handle; +pub mod heartbeat; +pub mod message; + +use super::*; +pub use gateway::*; +pub use handle::*; +use heartbeat::*; +pub use message::*; +use tokio_tungstenite::tungstenite::Message; + +use crate::errors::GatewayError; + +use async_trait::async_trait; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::{Arc, RwLock}; +use std::time::Duration; +use tokio::time::sleep_until; + +use futures_util::stream::SplitSink; +use futures_util::stream::SplitStream; +use log::{info, trace, warn}; +use tokio::net::TcpStream; +use tokio::sync::mpsc::Sender; +use tokio::sync::Mutex; +use tokio::task; +use tokio::task::JoinHandle; +use tokio::time; +use tokio::time::Instant; +use tokio_tungstenite::MaybeTlsStream; +use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; + +#[cfg(test)] +mod test { + use crate::types; + + use super::*; + use std::sync::atomic::{AtomicI32, Ordering::Relaxed}; + + #[derive(Debug)] + struct Consumer { + _name: String, + events_received: AtomicI32, + } + + #[async_trait] + impl Observer for Consumer { + async fn update(&self, _data: &types::GatewayResume) { + self.events_received.fetch_add(1, Relaxed); + } + } + + #[tokio::test] + async fn test_observer_behavior() { + let mut event = GatewayEvent::default(); + + let new_data = types::GatewayResume { + token: "token_3276ha37am3".to_string(), + session_id: "89346671230".to_string(), + seq: "3".to_string(), + }; + + let consumer = Arc::new(Consumer { + _name: "first".into(), + events_received: 0.into(), + }); + event.subscribe(consumer.clone()); + + let second_consumer = Arc::new(Consumer { + _name: "second".into(), + events_received: 0.into(), + }); + event.subscribe(second_consumer.clone()); + + event.notify(new_data.clone()).await; + event.unsubscribe(&*consumer); + event.notify(new_data).await; + + assert_eq!(consumer.events_received.load(Relaxed), 1); + assert_eq!(second_consumer.events_received.load(Relaxed), 2); + } +} diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs deleted file mode 100644 index 9f8ec38..0000000 --- a/src/gateway/gateway.rs +++ /dev/null @@ -1,645 +0,0 @@ -use self::event::Events; -use super::*; -use crate::types::{ - self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, - ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, - ThreadUpdate, UpdateMessage, WebSocketEvent, -}; - -pub type GatewayStore = Arc>>>>; - -#[allow(clippy::type_complexity)] -#[async_trait] -pub trait GatewayCapable -where - R: Stream, - S: Sink, - G: GatewayHandleCapable, - H: HeartbeatHandlerCapable + Send + Sync, -{ - fn get_events(&self) -> Arc>; - fn get_websocket_send(&self) -> Arc>>; - fn get_store(&self) -> GatewayStore; - fn get_url(&self) -> String; - fn get_heartbeat_handler(&self) -> &H; - /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] - /// - /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - async fn get_handle(websocket_url: String) -> Result; - async fn close(&mut self); - /// This handles a message as a websocket event and updates its events along with the events' observers - async fn handle_message(&mut self, msg: GatewayMessage) { - if msg.is_empty() { - return; - } - - if !msg.is_error() && !msg.is_payload() { - warn!( - "Message unrecognised: {:?}, please open an issue on the chorus github", - msg.message.to_string() - ); - return; - } - - if msg.is_error() { - let error = msg.error().unwrap(); - - warn!("GW: Received error {:?}, connection will close..", error); - - self.close().await; - - let events = self.get_events(); - let events = events.lock().await; - - events.error.notify(error).await; - - return; - } - - let gateway_payload = msg.payload().unwrap(); - println!("gateway payload: {:#?}", &gateway_payload); - - // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes - match gateway_payload.op_code { - // An event was dispatched, we need to look at the gateway event name t - GATEWAY_DISPATCH => { - let Some(event_name) = gateway_payload.event_name else { - warn!("Gateway dispatch op without event_name"); - return; - }; - - trace!("Gateway: Received {event_name}"); - - macro_rules! handle { - ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { - match event_name.as_str() { - $($name => { - let events = self.get_events(); - let event = &mut events.lock().await.$($path).+; - let json = gateway_payload.event_data.unwrap().get(); - match serde_json::from_str(json) { - Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), - Ok(message) => { - $( - let mut message: $message_type = message; - let store = self.get_store(); - let store = store.lock().await; - let id = if message.id().is_some() { - message.id().unwrap() - } else { - event.notify(message).await; - return; - }; - if let Some(to_update) = store.get(&id) { - let object = to_update.clone(); - let inner_object = object.read().unwrap(); - if let Some(_) = inner_object.downcast_ref::<$update_type>() { - let ptr = Arc::into_raw(object.clone()); - // SAFETY: - // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. - // - This operation doesn't read or write any shared data, and thus cannot cause a data race - // - The reference count is not being modified - let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; - drop(inner_object); - message.set_json(json.to_string()); - message.set_source_url(self.get_url().clone()); - message.update(downcasted.clone()); - } else { - warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) - } - } - )? - event.notify(message).await; - } - } - },)* - "RESUMED" => (), - "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); - match result { - Err(err) => { - warn!( - "Failed to parse gateway event {} ({})", - event_name, - err - ); - return; - } - Ok(sessions) => { - let events = self.get_events(); - let events = events.lock().await; - events.session.replace.notify( - types::SessionsReplace {sessions} - ).await; - } - } - }, - _ => { - warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); - } - } - }; - } - - // See https://discord.com/developers/docs/topics/gateway-events#receive-events - // "Some" of these are undocumented - handle!( - "READY" => session.ready, - "READY_SUPPLEMENTAL" => session.ready_supplemental, - "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, - "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, - "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule, - "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, - "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, - "CHANNEL_CREATE" => channel.create ChannelCreate: Guild, - "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, - "CHANNEL_UNREAD_UPDATE" => channel.unread_update, - "CHANNEL_DELETE" => channel.delete ChannelDelete: Guild, - "CHANNEL_PINS_UPDATE" => channel.pins_update, - "CALL_CREATE" => call.create, - "CALL_UPDATE" => call.update, - "CALL_DELETE" => call.delete, - "THREAD_CREATE" => thread.create, // TODO - "THREAD_UPDATE" => thread.update ThreadUpdate: Channel, - "THREAD_DELETE" => thread.delete, // TODO - "THREAD_LIST_SYNC" => thread.list_sync, // TODO - "THREAD_MEMBER_UPDATE" => thread.member_update, // TODO - "THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO - "GUILD_CREATE" => guild.create, // TODO - "GUILD_UPDATE" => guild.update, // TODO - "GUILD_DELETE" => guild.delete, // TODO - "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, - "GUILD_BAN_ADD" => guild.ban_add, // TODO - "GUILD_BAN_REMOVE" => guild.ban_remove, // TODO - "GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO - "GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO - "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, - "GUILD_MEMBER_ADD" => guild.member_add, - "GUILD_MEMBER_REMOVE" => guild.member_remove, - "GUILD_MEMBER_UPDATE" => guild.member_update, // TODO - "GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO - "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild, - "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject, - "GUILD_ROLE_DELETE" => guild.role_delete, // TODO - "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO - "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO - "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO - "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, - "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, - "PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO - "INTEGRATION_CREATE" => integration.create, // TODO - "INTEGRATION_UPDATE" => integration.update, // TODO - "INTEGRATION_DELETE" => integration.delete, // TODO - "INTERACTION_CREATE" => interaction.create, // TODO - "INVITE_CREATE" => invite.create, // TODO - "INVITE_DELETE" => invite.delete, // TODO - "MESSAGE_CREATE" => message.create, - "MESSAGE_UPDATE" => message.update, // TODO - "MESSAGE_DELETE" => message.delete, - "MESSAGE_DELETE_BULK" => message.delete_bulk, - "MESSAGE_REACTION_ADD" => message.reaction_add, // TODO - "MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO - "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO - "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO - "MESSAGE_ACK" => message.ack, - "PRESENCE_UPDATE" => user.presence_update, // TODO - "RELATIONSHIP_ADD" => relationship.add, - "RELATIONSHIP_REMOVE" => relationship.remove, - "STAGE_INSTANCE_CREATE" => stage_instance.create, - "STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO - "STAGE_INSTANCE_DELETE" => stage_instance.delete, - "TYPING_START" => user.typing_start, - "USER_UPDATE" => user.update, // TODO - "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, - "VOICE_STATE_UPDATE" => voice.state_update, // TODO - "VOICE_SERVER_UPDATE" => voice.server_update, - "WEBHOOKS_UPDATE" => webhooks.update - ); - } - // We received a heartbeat from the server - // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." - GATEWAY_HEARTBEAT => { - trace!("GW: Received Heartbeat // Heartbeat Request"); - - // Tell the heartbeat handler it should send a heartbeat right away - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT), - }; - - let heartbeat_thread_communicator = self.get_heartbeat_handler().get_send(); - - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_RECONNECT => { - todo!() - } - GATEWAY_INVALID_SESSION => { - todo!() - } - // Starts our heartbeat - // We should have already handled this in gateway init - GATEWAY_HELLO => { - warn!("Received hello when it was unexpected"); - } - GATEWAY_HEARTBEAT_ACK => { - trace!("GW: Received Heartbeat ACK"); - - // Tell the heartbeat handler we received an ack - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT_ACK), - }; - - let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.get_send(); - - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_IDENTIFY - | GATEWAY_UPDATE_PRESENCE - | GATEWAY_UPDATE_VOICE_STATE - | GATEWAY_RESUME - | GATEWAY_REQUEST_GUILD_MEMBERS - | GATEWAY_CALL_SYNC - | GATEWAY_LAZY_REQUEST => { - info!( - "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", - gateway_payload.op_code - ); - } - _ => { - warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); - } - } - - // If we we received a seq number we should let it know - if let Some(seq_num) = gateway_payload.sequence_number { - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: Some(seq_num), - // Op code is irrelevant here - op_code: None, - }; - - let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.get_send(); - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - } -} - -#[derive(Debug)] -pub struct DefaultGateway { - events: Arc>, - heartbeat_handler: DefaultHeartbeatHandler, - websocket_send: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - websocket_receive: SplitStream>>, - kill_send: tokio::sync::broadcast::Sender<()>, - store: GatewayStore, - url: String, -} - -#[async_trait] -impl - GatewayCapable< - WebSocketStream>, - WebSocketStream>, - DefaultGatewayHandle, - DefaultHeartbeatHandler, - > for DefaultGateway -{ - fn get_heartbeat_handler(&self) -> &DefaultHeartbeatHandler { - &self.heartbeat_handler - } - - #[allow(clippy::new_ret_no_self)] - async fn get_handle(websocket_url: String) -> Result { - let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") - { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - } - let (websocket_stream, _) = match connect_async_tls_with_config( - &websocket_url, - None, - false, - Some(Connector::Rustls( - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots) - .with_no_client_auth() - .into(), - )), - ) - .await - { - Ok(websocket_stream) => websocket_stream, - Err(e) => { - return Err(GatewayError::CannotConnect { - error: e.to_string(), - }) - } - }; - - let (websocket_send, mut websocket_receive) = websocket_stream.split(); - - let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); - - // Create a shared broadcast channel for killing all gateway tasks - let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); - - // Wait for the first hello and then spawn both tasks so we avoid nested tasks - // This automatically spawns the heartbeat task, but from the main thread - let msg = websocket_receive.next().await.unwrap().unwrap(); - let gateway_payload: types::GatewayReceivePayload = - serde_json::from_str(msg.to_text().unwrap()).unwrap(); - - if gateway_payload.op_code != GATEWAY_HELLO { - return Err(GatewayError::NonHelloOnInitiate { - opcode: gateway_payload.op_code, - }); - } - - info!("GW: Received Hello"); - - let gateway_hello: types::HelloData = - serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); - - let events = Events::default(); - let shared_events = Arc::new(Mutex::new(events)); - - let store = Arc::new(Mutex::new(HashMap::new())); - - let mut gateway = DefaultGateway { - events: shared_events.clone(), - heartbeat_handler: DefaultHeartbeatHandler::new( - Duration::from_millis(gateway_hello.heartbeat_interval), - shared_websocket_send.clone(), - kill_send.subscribe(), - ), - websocket_send: shared_websocket_send.clone(), - websocket_receive, - kill_send: kill_send.clone(), - store: store.clone(), - url: websocket_url.clone(), - }; - - // Now we can continuously check for messages in a different task, since we aren't going to receive another hello - task::spawn(async move { - gateway.gateway_listen_task().await; - }); - - Ok(DefaultGatewayHandle { - url: websocket_url.clone(), - events: shared_events, - websocket_send: shared_websocket_send.clone(), - kill_send: kill_send.clone(), - store, - }) - } - - /// Closes the websocket connection and stops all tasks - async fn close(&mut self) { - self.kill_send.send(()).unwrap(); - self.websocket_send.lock().await.close().await.unwrap(); - } - - fn get_events(&self) -> Arc> { - self.events.clone() - } - - fn get_websocket_send( - &self, - ) -> Arc>, Message>>> { - self.websocket_send.clone() - } - - fn get_store(&self) -> GatewayStore { - self.store.clone() - } - - fn get_url(&self) -> String { - self.url.clone() - } -} - -impl DefaultGateway { - /// The main gateway listener task; - /// - /// Can only be stopped by closing the websocket, cannot be made to listen for kill - pub async fn gateway_listen_task(&mut self) { - loop { - let msg = self.websocket_receive.next().await; - - // This if chain can be much better but if let is unstable on stable rust - if let Some(Ok(message)) = msg { - let _ = self - .handle_message(GatewayMessage::from_tungstenite_message(message)) - .await; - continue; - } - - // We couldn't receive the next message or it was an error, something is wrong with the websocket, close - warn!("GW: Websocket is broken, stopping gateway"); - break; - } - } - - /// Deserializes and updates a dispatched event, when we already know its type; - /// (Called for every event in handle_message) - #[allow(dead_code)] // TODO: Remove this allow annotation - async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( - data: &'a str, - event: &mut GatewayEvent, - ) -> Result<(), serde_json::Error> { - let data_deserialize_result: Result = serde_json::from_str(data); - - if data_deserialize_result.is_err() { - return Err(data_deserialize_result.err().unwrap()); - } - - event.notify(data_deserialize_result.unwrap()).await; - Ok(()) - } -} - -pub mod event { - use super::*; - - #[derive(Default, Debug)] - pub struct Events { - pub application: Application, - pub auto_moderation: AutoModeration, - pub session: Session, - pub message: Message, - pub user: User, - pub relationship: Relationship, - pub channel: Channel, - pub thread: Thread, - pub guild: Guild, - pub invite: Invite, - pub integration: Integration, - pub interaction: Interaction, - pub stage_instance: StageInstance, - pub call: Call, - pub voice: Voice, - pub webhooks: Webhooks, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, - pub error: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Application { - pub command_permissions_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct AutoModeration { - pub rule_create: GatewayEvent, - pub rule_update: GatewayEvent, - pub rule_delete: GatewayEvent, - pub action_execution: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Session { - pub ready: GatewayEvent, - pub ready_supplemental: GatewayEvent, - pub replace: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct StageInstance { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Message { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub delete_bulk: GatewayEvent, - pub reaction_add: GatewayEvent, - pub reaction_remove: GatewayEvent, - pub reaction_remove_all: GatewayEvent, - pub reaction_remove_emoji: GatewayEvent, - pub ack: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct User { - pub update: GatewayEvent, - pub guild_settings_update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Relationship { - pub add: GatewayEvent, - pub remove: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub unread_update: GatewayEvent, - pub delete: GatewayEvent, - pub pins_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Thread { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub list_sync: GatewayEvent, - pub member_update: GatewayEvent, - pub members_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Guild { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub audit_log_entry_create: GatewayEvent, - pub ban_add: GatewayEvent, - pub ban_remove: GatewayEvent, - pub emojis_update: GatewayEvent, - pub stickers_update: GatewayEvent, - pub integrations_update: GatewayEvent, - pub member_add: GatewayEvent, - pub member_remove: GatewayEvent, - pub member_update: GatewayEvent, - pub members_chunk: GatewayEvent, - pub role_create: GatewayEvent, - pub role_update: GatewayEvent, - pub role_delete: GatewayEvent, - pub role_scheduled_event_create: GatewayEvent, - pub role_scheduled_event_update: GatewayEvent, - pub role_scheduled_event_delete: GatewayEvent, - pub role_scheduled_event_user_add: GatewayEvent, - pub role_scheduled_event_user_remove: GatewayEvent, - pub passive_update_v1: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Invite { - pub create: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Integration { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Interaction { - pub create: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Call { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Voice { - pub state_update: GatewayEvent, - pub server_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Webhooks { - pub update: GatewayEvent, - } -} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index b5888b4..23dda75 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,39 +1,31 @@ -pub mod gateway; -pub mod handle; -pub mod heartbeat; -pub mod message; +pub mod default; -pub use gateway::*; -pub use handle::*; -use heartbeat::*; -pub use message::*; +use self::event::Events; +use crate::types::{ + self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, + ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, + Snowflake, SourceUrlField, ThreadUpdate, UpdateMessage, WebSocketEvent, +}; +use default::heartbeat::HeartbeatThreadCommunication; use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; -use crate::types::{Snowflake, WebSocketEvent}; use async_trait::async_trait; use std::any::Any; use std::collections::HashMap; -use std::fmt::Debug; use std::sync::{Arc, RwLock}; use std::time::Duration; -use tokio::time::sleep_until; +pub use default::*; use futures_util::stream::SplitSink; -use futures_util::stream::SplitStream; -use futures_util::{Sink, StreamExt}; +use futures_util::Sink; use futures_util::{SinkExt, Stream}; use log::{info, trace, warn}; -use tokio::net::TcpStream; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; -use tokio::task; -use tokio::task::JoinHandle; -use tokio::time; -use tokio::time::Instant; -use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; + +pub type GatewayStore = Arc>>>>; // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] @@ -136,53 +128,405 @@ impl GatewayEvent { } } -#[cfg(test)] -mod test { - use crate::types; +#[allow(clippy::type_complexity)] +#[async_trait] +pub trait GatewayCapable +where + R: Stream, + S: Sink, + G: GatewayHandleCapable, + H: HeartbeatHandlerCapable + Send + Sync, +{ + fn get_events(&self) -> Arc>; + fn get_websocket_send(&self) -> Arc>>; + fn get_store(&self) -> GatewayStore; + fn get_url(&self) -> String; + fn get_heartbeat_handler(&self) -> &H; + /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] + /// + /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation + async fn get_handle(websocket_url: String) -> Result; + async fn close(&mut self); + /// This handles a message as a websocket event and updates its events along with the events' observers + async fn handle_message(&mut self, msg: GatewayMessage) { + if msg.is_empty() { + return; + } - use super::*; - use std::sync::atomic::{AtomicI32, Ordering::Relaxed}; + if !msg.is_error() && !msg.is_payload() { + warn!( + "Message unrecognised: {:?}, please open an issue on the chorus github", + msg.message.to_string() + ); + return; + } - #[derive(Debug)] - struct Consumer { - _name: String, - events_received: AtomicI32, - } + if msg.is_error() { + let error = msg.error().unwrap(); - #[async_trait] - impl Observer for Consumer { - async fn update(&self, _data: &types::GatewayResume) { - self.events_received.fetch_add(1, Relaxed); + warn!("GW: Received error {:?}, connection will close..", error); + + self.close().await; + + let events = self.get_events(); + let events = events.lock().await; + + events.error.notify(error).await; + + return; + } + + let gateway_payload = msg.payload().unwrap(); + println!("gateway payload: {:#?}", &gateway_payload); + + // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes + match gateway_payload.op_code { + // An event was dispatched, we need to look at the gateway event name t + GATEWAY_DISPATCH => { + let Some(event_name) = gateway_payload.event_name else { + warn!("Gateway dispatch op without event_name"); + return; + }; + + trace!("Gateway: Received {event_name}"); + + macro_rules! handle { + ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { + match event_name.as_str() { + $($name => { + let events = self.get_events(); + let event = &mut events.lock().await.$($path).+; + let json = gateway_payload.event_data.unwrap().get(); + match serde_json::from_str(json) { + Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), + Ok(message) => { + $( + let mut message: $message_type = message; + let store = self.get_store(); + let store = store.lock().await; + let id = if message.id().is_some() { + message.id().unwrap() + } else { + event.notify(message).await; + return; + }; + if let Some(to_update) = store.get(&id) { + let object = to_update.clone(); + let inner_object = object.read().unwrap(); + if let Some(_) = inner_object.downcast_ref::<$update_type>() { + let ptr = Arc::into_raw(object.clone()); + // SAFETY: + // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. + // - This operation doesn't read or write any shared data, and thus cannot cause a data race + // - The reference count is not being modified + let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; + drop(inner_object); + message.set_json(json.to_string()); + message.set_source_url(self.get_url().clone()); + message.update(downcasted.clone()); + } else { + warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) + } + } + )? + event.notify(message).await; + } + } + },)* + "RESUMED" => (), + "SESSIONS_REPLACE" => { + let result: Result, serde_json::Error> = + serde_json::from_str(gateway_payload.event_data.unwrap().get()); + match result { + Err(err) => { + warn!( + "Failed to parse gateway event {} ({})", + event_name, + err + ); + return; + } + Ok(sessions) => { + let events = self.get_events(); + let events = events.lock().await; + events.session.replace.notify( + types::SessionsReplace {sessions} + ).await; + } + } + }, + _ => { + warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); + } + } + }; + } + + // See https://discord.com/developers/docs/topics/gateway-events#receive-events + // "Some" of these are undocumented + handle!( + "READY" => session.ready, + "READY_SUPPLEMENTAL" => session.ready_supplemental, + "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, + "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, + "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule, + "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, + "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, + "CHANNEL_CREATE" => channel.create ChannelCreate: Guild, + "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, + "CHANNEL_UNREAD_UPDATE" => channel.unread_update, + "CHANNEL_DELETE" => channel.delete ChannelDelete: Guild, + "CHANNEL_PINS_UPDATE" => channel.pins_update, + "CALL_CREATE" => call.create, + "CALL_UPDATE" => call.update, + "CALL_DELETE" => call.delete, + "THREAD_CREATE" => thread.create, // TODO + "THREAD_UPDATE" => thread.update ThreadUpdate: Channel, + "THREAD_DELETE" => thread.delete, // TODO + "THREAD_LIST_SYNC" => thread.list_sync, // TODO + "THREAD_MEMBER_UPDATE" => thread.member_update, // TODO + "THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO + "GUILD_CREATE" => guild.create, // TODO + "GUILD_UPDATE" => guild.update, // TODO + "GUILD_DELETE" => guild.delete, // TODO + "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, + "GUILD_BAN_ADD" => guild.ban_add, // TODO + "GUILD_BAN_REMOVE" => guild.ban_remove, // TODO + "GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO + "GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO + "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, + "GUILD_MEMBER_ADD" => guild.member_add, + "GUILD_MEMBER_REMOVE" => guild.member_remove, + "GUILD_MEMBER_UPDATE" => guild.member_update, // TODO + "GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO + "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild, + "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject, + "GUILD_ROLE_DELETE" => guild.role_delete, // TODO + "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO + "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO + "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO + "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, + "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, + "PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO + "INTEGRATION_CREATE" => integration.create, // TODO + "INTEGRATION_UPDATE" => integration.update, // TODO + "INTEGRATION_DELETE" => integration.delete, // TODO + "INTERACTION_CREATE" => interaction.create, // TODO + "INVITE_CREATE" => invite.create, // TODO + "INVITE_DELETE" => invite.delete, // TODO + "MESSAGE_CREATE" => message.create, + "MESSAGE_UPDATE" => message.update, // TODO + "MESSAGE_DELETE" => message.delete, + "MESSAGE_DELETE_BULK" => message.delete_bulk, + "MESSAGE_REACTION_ADD" => message.reaction_add, // TODO + "MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO + "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO + "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO + "MESSAGE_ACK" => message.ack, + "PRESENCE_UPDATE" => user.presence_update, // TODO + "RELATIONSHIP_ADD" => relationship.add, + "RELATIONSHIP_REMOVE" => relationship.remove, + "STAGE_INSTANCE_CREATE" => stage_instance.create, + "STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO + "STAGE_INSTANCE_DELETE" => stage_instance.delete, + "TYPING_START" => user.typing_start, + "USER_UPDATE" => user.update, // TODO + "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, + "VOICE_STATE_UPDATE" => voice.state_update, // TODO + "VOICE_SERVER_UPDATE" => voice.server_update, + "WEBHOOKS_UPDATE" => webhooks.update + ); + } + // We received a heartbeat from the server + // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." + GATEWAY_HEARTBEAT => { + trace!("GW: Received Heartbeat // Heartbeat Request"); + + // Tell the heartbeat handler it should send a heartbeat right away + + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: gateway_payload.sequence_number, + op_code: Some(GATEWAY_HEARTBEAT), + }; + + let heartbeat_thread_communicator = self.get_heartbeat_handler().get_send(); + + heartbeat_thread_communicator + .send(heartbeat_communication) + .await + .unwrap(); + } + GATEWAY_RECONNECT => { + todo!() + } + GATEWAY_INVALID_SESSION => { + todo!() + } + // Starts our heartbeat + // We should have already handled this in gateway init + GATEWAY_HELLO => { + warn!("Received hello when it was unexpected"); + } + GATEWAY_HEARTBEAT_ACK => { + trace!("GW: Received Heartbeat ACK"); + + // Tell the heartbeat handler we received an ack + + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: gateway_payload.sequence_number, + op_code: Some(GATEWAY_HEARTBEAT_ACK), + }; + + let heartbeat_handler = self.get_heartbeat_handler(); + let heartbeat_thread_communicator = heartbeat_handler.get_send(); + + heartbeat_thread_communicator + .send(heartbeat_communication) + .await + .unwrap(); + } + GATEWAY_IDENTIFY + | GATEWAY_UPDATE_PRESENCE + | GATEWAY_UPDATE_VOICE_STATE + | GATEWAY_RESUME + | GATEWAY_REQUEST_GUILD_MEMBERS + | GATEWAY_CALL_SYNC + | GATEWAY_LAZY_REQUEST => { + info!( + "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", + gateway_payload.op_code + ); + } + _ => { + warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + } + } + + // If we we received a seq number we should let it know + if let Some(seq_num) = gateway_payload.sequence_number { + let heartbeat_communication = HeartbeatThreadCommunication { + sequence_number: Some(seq_num), + // Op code is irrelevant here + op_code: None, + }; + + let heartbeat_handler = self.get_heartbeat_handler(); + let heartbeat_thread_communicator = heartbeat_handler.get_send(); + heartbeat_thread_communicator + .send(heartbeat_communication) + .await + .unwrap(); } } - - #[tokio::test] - async fn test_observer_behavior() { - let mut event = GatewayEvent::default(); - - let new_data = types::GatewayResume { - token: "token_3276ha37am3".to_string(), - session_id: "89346671230".to_string(), - seq: "3".to_string(), - }; - - let consumer = Arc::new(Consumer { - _name: "first".into(), - events_received: 0.into(), - }); - event.subscribe(consumer.clone()); - - let second_consumer = Arc::new(Consumer { - _name: "second".into(), - events_received: 0.into(), - }); - event.subscribe(second_consumer.clone()); - - event.notify(new_data.clone()).await; - event.unsubscribe(&*consumer); - event.notify(new_data).await; - - assert_eq!(consumer.events_received.load(Relaxed), 1); - assert_eq!(second_consumer.events_received.load(Relaxed), 2); - } +} + +#[async_trait(?Send)] +pub trait GatewayHandleCapable +where + R: Stream, + S: Sink, +{ + /// Sends json to the gateway with an opcode + async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value); + + /// Observes an Item ``, which will update itself, if new information about this + /// item arrives on the corresponding Gateway Thread + async fn observe + Send + Sync>( + &self, + object: Arc>, + ) -> Arc>; + + /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` + /// with all of its observable fields being observed. + async fn observe_and_into_inner>( + &self, + object: Arc>, + ) -> T { + let channel = self.observe(object.clone()).await; + let object = channel.read().unwrap().clone(); + object + } + + /// Sends an identify event to the gateway + async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Identify.."); + + self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; + } + + /// Sends an update presence event to the gateway + async fn send_update_presence(&self, to_send: types::UpdatePresence) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Update Presence.."); + + self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) + .await; + } + + /// Sends a resume event to the gateway + async fn send_resume(&self, to_send: types::GatewayResume) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Resume.."); + + self.send_json_event(GATEWAY_RESUME, to_send_value).await; + } + + /// Sends a request guild members to the server + async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Request Guild Members.."); + + self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) + .await; + } + + /// Sends an update voice state to the server + async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { + let to_send_value = serde_json::to_value(to_send).unwrap(); + + trace!("GW: Sending Update Voice State.."); + + self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) + .await; + } + + /// Sends a call sync to the server + async fn send_call_sync(&self, to_send: types::CallSync) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Call Sync.."); + + self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; + } + + /// Sends a Lazy Request + async fn send_lazy_request(&self, to_send: types::LazyRequest) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Lazy Request.."); + + self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) + .await; + } + + /// Closes the websocket connection and stops all gateway tasks; + /// + /// Esentially pulls the plug on the gateway, leaving it possible to resume; + async fn close(&self); +} + +pub trait HeartbeatHandlerCapable> { + fn new( + heartbeat_interval: Duration, + websocket_tx: Arc>>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> Self; + + fn get_send(&self) -> &Sender; + fn get_heartbeat_interval(&self) -> Duration; } From b84d4519b79b364d27da7aa648af94cfcdb40c14 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:31:44 +0100 Subject: [PATCH 026/130] Change typedef to only compile with feature client --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7c1927f..5bb866b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,9 @@ #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type Gateway = DefaultGateway; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type GatewayHandle = DefaultGatewayHandle; use gateway::DefaultGateway; From d328dcf314e093ee4dfd38a13164e3decabb00cc Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:32:13 +0100 Subject: [PATCH 027/130] Conditionally compile wasm/default --- src/gateway/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 23dda75..92ee61e 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,29 +1,33 @@ pub mod default; +pub mod wasm; + +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub use default::*; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub use wasm::*; use self::event::Events; +use crate::errors::GatewayError; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, Snowflake, SourceUrlField, ThreadUpdate, UpdateMessage, WebSocketEvent, }; -use default::heartbeat::HeartbeatThreadCommunication; -use tokio_tungstenite::tungstenite::Message; -use crate::errors::GatewayError; - -use async_trait::async_trait; use std::any::Any; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::time::Duration; -pub use default::*; +use async_trait::async_trait; +use default::heartbeat::HeartbeatThreadCommunication; use futures_util::stream::SplitSink; use futures_util::Sink; use futures_util::{SinkExt, Stream}; use log::{info, trace, warn}; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; +use tokio_tungstenite::tungstenite::Message; pub type GatewayStore = Arc>>>>; From 14fa9a929c4b9467aaac3e19fcf443cbe5a9b920 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:32:31 +0100 Subject: [PATCH 028/130] Add wasm gateway submodule --- src/gateway/wasm/gateway.rs | 1 + src/gateway/wasm/mod.rs | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 src/gateway/wasm/gateway.rs create mode 100644 src/gateway/wasm/mod.rs diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/gateway/wasm/gateway.rs @@ -0,0 +1 @@ + diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs new file mode 100644 index 0000000..73a5181 --- /dev/null +++ b/src/gateway/wasm/mod.rs @@ -0,0 +1,2 @@ +pub mod gateway; +pub use gateway::*; From e9bf3b32a0ecc173dcbceaa934eecc166ccba303 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:23:44 +0100 Subject: [PATCH 029/130] Move heartbeat to shared location --- src/gateway/default/heartbeat.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs index b737366..4c30496 100644 --- a/src/gateway/default/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -2,9 +2,6 @@ use crate::types; use super::*; -/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; - /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used #[derive(Debug)] From 739f2c8b71b634812ee8a6c73a6411c94e93c6ed Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:24:02 +0100 Subject: [PATCH 030/130] Create wasm gateway module --- src/gateway/wasm/gateway.rs | 2 +- src/gateway/wasm/heartbeat.rs | 14 ++++++++++++++ src/gateway/wasm/mod.rs | 5 ++++- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/gateway/wasm/heartbeat.rs diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index 8b13789..5a54efb 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -1 +1 @@ - +use ws_stream_wasm::*; diff --git a/src/gateway/wasm/heartbeat.rs b/src/gateway/wasm/heartbeat.rs new file mode 100644 index 0000000..b7346fa --- /dev/null +++ b/src/gateway/wasm/heartbeat.rs @@ -0,0 +1,14 @@ +use tokio::task::JoinHandle; + +use super::*; + +#[allow(dead_code)] // FIXME: Remove this, once used +#[derive(Debug)] +pub struct WasmHeartbeatHandler { + /// How ofter heartbeats need to be sent at a minimum + pub heartbeat_interval: Duration, + /// The send channel for the heartbeat thread + pub send: Sender, + /// The handle of the thread + handle: JoinHandle<()>, +} diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs index 73a5181..cdcf007 100644 --- a/src/gateway/wasm/mod.rs +++ b/src/gateway/wasm/mod.rs @@ -1,2 +1,5 @@ pub mod gateway; -pub use gateway::*; +pub mod heartbeat; +use super::*; +use gateway::*; +use heartbeat::*; From 1fdb01e84662bf9e7b8ddf144944162cb5ee08f2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:24:25 +0100 Subject: [PATCH 031/130] Move events to shared location --- src/gateway/default/gateway.rs | 164 +-------------------------------- src/gateway/default/handle.rs | 2 +- src/gateway/events.rs | 159 ++++++++++++++++++++++++++++++++ src/gateway/mod.rs | 8 +- 4 files changed, 168 insertions(+), 165 deletions(-) create mode 100644 src/gateway/events.rs diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index a90a2e9..49ce89e 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -1,6 +1,6 @@ use futures_util::StreamExt; -use self::event::Events; +use super::events::Events; use super::*; use crate::types::{self, WebSocketEvent}; @@ -185,165 +185,3 @@ impl DefaultGateway { Ok(()) } } - -pub mod event { - use super::*; - - #[derive(Default, Debug)] - pub struct Events { - pub application: Application, - pub auto_moderation: AutoModeration, - pub session: Session, - pub message: Message, - pub user: User, - pub relationship: Relationship, - pub channel: Channel, - pub thread: Thread, - pub guild: Guild, - pub invite: Invite, - pub integration: Integration, - pub interaction: Interaction, - pub stage_instance: StageInstance, - pub call: Call, - pub voice: Voice, - pub webhooks: Webhooks, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, - pub error: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Application { - pub command_permissions_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct AutoModeration { - pub rule_create: GatewayEvent, - pub rule_update: GatewayEvent, - pub rule_delete: GatewayEvent, - pub action_execution: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Session { - pub ready: GatewayEvent, - pub ready_supplemental: GatewayEvent, - pub replace: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct StageInstance { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Message { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub delete_bulk: GatewayEvent, - pub reaction_add: GatewayEvent, - pub reaction_remove: GatewayEvent, - pub reaction_remove_all: GatewayEvent, - pub reaction_remove_emoji: GatewayEvent, - pub ack: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct User { - pub update: GatewayEvent, - pub guild_settings_update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Relationship { - pub add: GatewayEvent, - pub remove: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub unread_update: GatewayEvent, - pub delete: GatewayEvent, - pub pins_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Thread { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub list_sync: GatewayEvent, - pub member_update: GatewayEvent, - pub members_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Guild { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub audit_log_entry_create: GatewayEvent, - pub ban_add: GatewayEvent, - pub ban_remove: GatewayEvent, - pub emojis_update: GatewayEvent, - pub stickers_update: GatewayEvent, - pub integrations_update: GatewayEvent, - pub member_add: GatewayEvent, - pub member_remove: GatewayEvent, - pub member_update: GatewayEvent, - pub members_chunk: GatewayEvent, - pub role_create: GatewayEvent, - pub role_update: GatewayEvent, - pub role_delete: GatewayEvent, - pub role_scheduled_event_create: GatewayEvent, - pub role_scheduled_event_update: GatewayEvent, - pub role_scheduled_event_delete: GatewayEvent, - pub role_scheduled_event_user_add: GatewayEvent, - pub role_scheduled_event_user_remove: GatewayEvent, - pub passive_update_v1: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Invite { - pub create: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Integration { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Interaction { - pub create: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Call { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Voice { - pub state_update: GatewayEvent, - pub server_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Webhooks { - pub update: GatewayEvent, - } -} diff --git a/src/gateway/default/handle.rs b/src/gateway/default/handle.rs index 14eb9cb..e76c0f1 100644 --- a/src/gateway/default/handle.rs +++ b/src/gateway/default/handle.rs @@ -1,4 +1,4 @@ -use super::{event::Events, *}; +use super::{events::Events, *}; use crate::types::{self, Composite}; #[async_trait(?Send)] diff --git a/src/gateway/events.rs b/src/gateway/events.rs new file mode 100644 index 0000000..bc8565e --- /dev/null +++ b/src/gateway/events.rs @@ -0,0 +1,159 @@ +use super::*; + +#[derive(Default, Debug)] +pub struct Events { + pub application: Application, + pub auto_moderation: AutoModeration, + pub session: Session, + pub message: Message, + pub user: User, + pub relationship: Relationship, + pub channel: Channel, + pub thread: Thread, + pub guild: Guild, + pub invite: Invite, + pub integration: Integration, + pub interaction: Interaction, + pub stage_instance: StageInstance, + pub call: Call, + pub voice: Voice, + pub webhooks: Webhooks, + pub gateway_identify_payload: GatewayEvent, + pub gateway_resume: GatewayEvent, + pub error: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Application { + pub command_permissions_update: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct AutoModeration { + pub rule_create: GatewayEvent, + pub rule_update: GatewayEvent, + pub rule_delete: GatewayEvent, + pub action_execution: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Session { + pub ready: GatewayEvent, + pub ready_supplemental: GatewayEvent, + pub replace: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct StageInstance { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Message { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub delete_bulk: GatewayEvent, + pub reaction_add: GatewayEvent, + pub reaction_remove: GatewayEvent, + pub reaction_remove_all: GatewayEvent, + pub reaction_remove_emoji: GatewayEvent, + pub ack: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct User { + pub update: GatewayEvent, + pub guild_settings_update: GatewayEvent, + pub presence_update: GatewayEvent, + pub typing_start: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Relationship { + pub add: GatewayEvent, + pub remove: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Channel { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub unread_update: GatewayEvent, + pub delete: GatewayEvent, + pub pins_update: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Thread { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub list_sync: GatewayEvent, + pub member_update: GatewayEvent, + pub members_update: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Guild { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub audit_log_entry_create: GatewayEvent, + pub ban_add: GatewayEvent, + pub ban_remove: GatewayEvent, + pub emojis_update: GatewayEvent, + pub stickers_update: GatewayEvent, + pub integrations_update: GatewayEvent, + pub member_add: GatewayEvent, + pub member_remove: GatewayEvent, + pub member_update: GatewayEvent, + pub members_chunk: GatewayEvent, + pub role_create: GatewayEvent, + pub role_update: GatewayEvent, + pub role_delete: GatewayEvent, + pub role_scheduled_event_create: GatewayEvent, + pub role_scheduled_event_update: GatewayEvent, + pub role_scheduled_event_delete: GatewayEvent, + pub role_scheduled_event_user_add: GatewayEvent, + pub role_scheduled_event_user_remove: GatewayEvent, + pub passive_update_v1: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Invite { + pub create: GatewayEvent, + pub delete: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Integration { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Interaction { + pub create: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Call { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Voice { + pub state_update: GatewayEvent, + pub server_update: GatewayEvent, +} + +#[derive(Default, Debug)] +pub struct Webhooks { + pub update: GatewayEvent, +} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 92ee61e..c33e7cb 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,4 +1,7 @@ +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub mod default; +pub mod events; +#[cfg(all(target_arch = "wasm32", feature = "client"))] pub mod wasm; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] @@ -6,7 +9,7 @@ pub use default::*; #[cfg(all(target_arch = "wasm32", feature = "client"))] pub use wasm::*; -use self::event::Events; +use self::events::Events; use crate::errors::GatewayError; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, @@ -31,6 +34,9 @@ use tokio_tungstenite::tungstenite::Message; pub type GatewayStore = Arc>>>>; +/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms +const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; + // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] const GATEWAY_DISPATCH: u8 = 0; From b27f60b989db1b1f70477cebeb68c1030c33993b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:28:55 +0100 Subject: [PATCH 032/130] Move ws_stream_wasm to shared dependencies Avoids conditional compiling bug i haven't yet fully found out. See: https://cloud.bitfl0wer.de/s/KEcZs7PtQ3JZo8x --- Cargo.lock | 68 ------------------------------------------------------ Cargo.toml | 4 ++-- 2 files changed, 2 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7a6cdd..ba6758d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,7 +211,6 @@ dependencies = [ "reqwest", "rustls", "rustls-native-certs", - "rusty-hook", "safina-timer", "serde", "serde-aux", @@ -252,15 +251,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "ci_info" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e" -dependencies = [ - "envmnt", -] - [[package]] name = "const-oid" version = "0.9.5" @@ -440,16 +430,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "envmnt" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059" -dependencies = [ - "fsio", - "indexmap 1.9.3", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -536,12 +516,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsio" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" - [[package]] name = "futures" version = "0.3.29" @@ -652,15 +626,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - [[package]] name = "getrandom" version = "0.2.11" @@ -1128,12 +1093,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nias" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" - [[package]] name = "nom" version = "7.1.3" @@ -1690,18 +1649,6 @@ dependencies = [ "untrusted 0.9.0", ] -[[package]] -name = "rusty-hook" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cee9be61be7e1cbadd851e58ed7449c29c620f00b23df937cb9cbc04ac21a3" -dependencies = [ - "ci_info", - "getopts", - "nias", - "toml", -] - [[package]] name = "ryu" version = "1.0.15" @@ -2419,15 +2366,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.6.5" @@ -2560,12 +2498,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - [[package]] name = "unicode_categories" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5bae865..5072d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" +ws_stream_wasm = "0.7.4" + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" @@ -66,10 +68,8 @@ native-tls = "0.2.11" hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] -ws_stream_wasm = "0.7.4" getrandom = { version = "0.2.11", features = ["js"] } tokio-tungstenite = { version = "0.20.1", default-features = false } [dev-dependencies] lazy_static = "1.4.0" -rusty-hook = "0.11.2" From f8795adc350b46fd7e2623ec26bf197b6d208341 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 17:47:40 +0100 Subject: [PATCH 033/130] Make message.rs shared Make HeartbeatThreadCommunication shared Move shared code to mod.rs Export new shared code Remove useless export --- src/gateway/default/heartbeat.rs | 10 ---------- src/gateway/default/mod.rs | 2 -- src/gateway/{default => }/message.rs | 0 src/gateway/mod.rs | 13 ++++++++++++- 4 files changed, 12 insertions(+), 13 deletions(-) rename src/gateway/{default => }/message.rs (100%) diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs index 4c30496..8a6a8b5 100644 --- a/src/gateway/default/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -146,13 +146,3 @@ impl DefaultHeartbeatHandler { } } } - -/// Used for communications between the heartbeat and gateway thread. -/// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server -#[derive(Clone, Copy, Debug)] -pub struct HeartbeatThreadCommunication { - /// The opcode for the communication we received, if relevant - pub op_code: Option, - /// The sequence number we got from discord, if any - pub sequence_number: Option, -} diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index 4843b96..8ec29e8 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -1,13 +1,11 @@ pub mod gateway; pub mod handle; pub mod heartbeat; -pub mod message; use super::*; pub use gateway::*; pub use handle::*; use heartbeat::*; -pub use message::*; use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; diff --git a/src/gateway/default/message.rs b/src/gateway/message.rs similarity index 100% rename from src/gateway/default/message.rs rename to src/gateway/message.rs diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index c33e7cb..bdf150d 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,11 +1,13 @@ #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub mod default; pub mod events; +pub mod message; #[cfg(all(target_arch = "wasm32", feature = "client"))] pub mod wasm; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub use default::*; +pub use message::*; #[cfg(all(target_arch = "wasm32", feature = "client"))] pub use wasm::*; @@ -23,7 +25,6 @@ use std::sync::{Arc, RwLock}; use std::time::Duration; use async_trait::async_trait; -use default::heartbeat::HeartbeatThreadCommunication; use futures_util::stream::SplitSink; use futures_util::Sink; use futures_util::{SinkExt, Stream}; @@ -87,6 +88,16 @@ const GATEWAY_LAZY_REQUEST: u8 = 14; pub type ObservableObject = dyn Send + Sync + Any; +/// Used for communications between the heartbeat and gateway thread. +/// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server +#[derive(Clone, Copy, Debug)] +pub struct HeartbeatThreadCommunication { + /// The opcode for the communication we received, if relevant + pub op_code: Option, + /// The sequence number we got from discord, if any + pub sequence_number: Option, +} + /// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. pub trait Updateable: 'static + Send + Sync { fn id(&self) -> Snowflake; From 6106ac41ae23dd9a86c5fe7bb2f1909e334e4429 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:20:11 +0100 Subject: [PATCH 034/130] Add import --- src/gateway/wasm/heartbeat.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gateway/wasm/heartbeat.rs b/src/gateway/wasm/heartbeat.rs index b7346fa..86d9573 100644 --- a/src/gateway/wasm/heartbeat.rs +++ b/src/gateway/wasm/heartbeat.rs @@ -1,7 +1,9 @@ use tokio::task::JoinHandle; +use ws_stream_wasm::*; use super::*; +/// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once used #[derive(Debug)] pub struct WasmHeartbeatHandler { From f3558371257a994eaa0d33d5b0230b9e1b121a66 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:20:45 +0100 Subject: [PATCH 035/130] Add Adapter GatewayMessageData Fits onto tokio_tungstenite Messages and ws_stream_wasm::WsMessage --- src/gateway/message.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 12d0e1a..34218f8 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,7 +1,56 @@ +use std::str::Utf8Error; + use crate::types; use super::*; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum GatewayMessageData { + Text(String), + Binary(Vec), +} + +impl From for GatewayMessageData { + fn from(value: tokio_tungstenite::tungstenite::Message) -> Self { + match value { + Message::Text(string) => Self::Text(string), + Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => Self::Binary(data), + Message::Close(data) => { + if let Some(data) = data { + Self::Text(data.code.to_string()) + } else { + Self::Text(String::new()) + } + } + Message::Frame(data) => Self::Binary(data.into_data()), + } + } +} + +impl From for GatewayMessageData { + fn from(value: ws_stream_wasm::WsMessage) -> Self { + match value { + ws_stream_wasm::WsMessage::Text(string) => Self::Text(string), + ws_stream_wasm::WsMessage::Binary(data) => Self::Binary(data), + } + } +} + +impl From for GatewayMessageData { + fn from(value: String) -> Self { + Self::Text(value) + } +} + +impl GatewayMessageData { + pub fn to_text(&self) -> Result<&str, Utf8Error> { + match *self { + GatewayMessageData::Text(ref text) => Ok(text), + GatewayMessageData::Binary(ref data) => Ok(std::str::from_utf8(data)?), + } + } +} + /// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] From b0b90f094e03cceb7bca1bf3204fc27afb537101 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:30:09 +0100 Subject: [PATCH 036/130] Make GatewayMessage use new Adapter Type --- src/gateway/message.rs | 47 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 34218f8..747d53a 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,9 +1,12 @@ +use std::fmt::Display; use std::str::Utf8Error; use crate::types; use super::*; +/// An Adapter type for [tokio_tungstenite::tungstenite::Message] and [ws_stream_wasm::WsMessage]. +/// Represents a message received from the gateway. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum GatewayMessageData { Text(String), @@ -43,12 +46,42 @@ impl From for GatewayMessageData { } impl GatewayMessageData { + /// Converts self to a string slice, if possible pub fn to_text(&self) -> Result<&str, Utf8Error> { match *self { GatewayMessageData::Text(ref text) => Ok(text), GatewayMessageData::Binary(ref data) => Ok(std::str::from_utf8(data)?), } } + + /// Returns the length of the message + pub fn len(&self) -> usize { + match *self { + Self::Text(ref string) => string.len(), + Self::Binary(ref data) => data.len(), + } + } + + /// Returns true if the WebSocket message is text. + pub fn is_text(&self) -> bool { + matches!(*self, Self::Binary(_)) + } + + /// Returns true if the WebSocket message has no content. + /// For example, if the other side of the connection sent an empty string. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Display for GatewayMessageData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Ok(string) = self.to_text() { + write!(f, "{}", string) + } else { + write!(f, "Binary Data", self.len()) + } + } } /// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. @@ -56,13 +89,21 @@ impl GatewayMessageData { #[derive(Clone, Debug)] pub struct GatewayMessage { /// The message we received from the server - pub(crate) message: tokio_tungstenite::tungstenite::Message, + pub(crate) message: GatewayMessageData, } impl GatewayMessage { /// Creates self from a tungstenite message pub fn from_tungstenite_message(message: tokio_tungstenite::tungstenite::Message) -> Self { - Self { message } + Self { + message: GatewayMessageData::from(message), + } + } + + pub fn from_ws_stream_wasm_message(message: ws_stream_wasm::WsMessage) -> Self { + Self { + message: GatewayMessageData::from(message), + } } /// Parses the message as an error; @@ -108,7 +149,7 @@ impl GatewayMessage { /// Returns whether or not the message is a payload pub fn is_payload(&self) -> bool { // close messages are never payloads, payloads are only text messages - if self.message.is_close() | !self.message.is_text() { + if !self.message.is_text() { return false; } From e38445b6446c8e59095bd2e471e4c64a8ffced5c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:05:36 +0100 Subject: [PATCH 037/130] Revert "Make GatewayMessage use new Adapter Type" This reverts commit b0b90f094e03cceb7bca1bf3204fc27afb537101. --- src/gateway/message.rs | 47 +++--------------------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 747d53a..34218f8 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,12 +1,9 @@ -use std::fmt::Display; use std::str::Utf8Error; use crate::types; use super::*; -/// An Adapter type for [tokio_tungstenite::tungstenite::Message] and [ws_stream_wasm::WsMessage]. -/// Represents a message received from the gateway. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum GatewayMessageData { Text(String), @@ -46,42 +43,12 @@ impl From for GatewayMessageData { } impl GatewayMessageData { - /// Converts self to a string slice, if possible pub fn to_text(&self) -> Result<&str, Utf8Error> { match *self { GatewayMessageData::Text(ref text) => Ok(text), GatewayMessageData::Binary(ref data) => Ok(std::str::from_utf8(data)?), } } - - /// Returns the length of the message - pub fn len(&self) -> usize { - match *self { - Self::Text(ref string) => string.len(), - Self::Binary(ref data) => data.len(), - } - } - - /// Returns true if the WebSocket message is text. - pub fn is_text(&self) -> bool { - matches!(*self, Self::Binary(_)) - } - - /// Returns true if the WebSocket message has no content. - /// For example, if the other side of the connection sent an empty string. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Display for GatewayMessageData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Ok(string) = self.to_text() { - write!(f, "{}", string) - } else { - write!(f, "Binary Data", self.len()) - } - } } /// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. @@ -89,21 +56,13 @@ impl Display for GatewayMessageData { #[derive(Clone, Debug)] pub struct GatewayMessage { /// The message we received from the server - pub(crate) message: GatewayMessageData, + pub(crate) message: tokio_tungstenite::tungstenite::Message, } impl GatewayMessage { /// Creates self from a tungstenite message pub fn from_tungstenite_message(message: tokio_tungstenite::tungstenite::Message) -> Self { - Self { - message: GatewayMessageData::from(message), - } - } - - pub fn from_ws_stream_wasm_message(message: ws_stream_wasm::WsMessage) -> Self { - Self { - message: GatewayMessageData::from(message), - } + Self { message } } /// Parses the message as an error; @@ -149,7 +108,7 @@ impl GatewayMessage { /// Returns whether or not the message is a payload pub fn is_payload(&self) -> bool { // close messages are never payloads, payloads are only text messages - if !self.message.is_text() { + if self.message.is_close() | !self.message.is_text() { return false; } From 339a8f5b7c52ecd21371193a7c9ffb1c9360f1ca Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:07:14 +0100 Subject: [PATCH 038/130] Revert "Add Adapter GatewayMessageData" This reverts commit f3558371257a994eaa0d33d5b0230b9e1b121a66. --- src/gateway/message.rs | 49 ------------------------------------------ 1 file changed, 49 deletions(-) diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 34218f8..12d0e1a 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,56 +1,7 @@ -use std::str::Utf8Error; - use crate::types; use super::*; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GatewayMessageData { - Text(String), - Binary(Vec), -} - -impl From for GatewayMessageData { - fn from(value: tokio_tungstenite::tungstenite::Message) -> Self { - match value { - Message::Text(string) => Self::Text(string), - Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => Self::Binary(data), - Message::Close(data) => { - if let Some(data) = data { - Self::Text(data.code.to_string()) - } else { - Self::Text(String::new()) - } - } - Message::Frame(data) => Self::Binary(data.into_data()), - } - } -} - -impl From for GatewayMessageData { - fn from(value: ws_stream_wasm::WsMessage) -> Self { - match value { - ws_stream_wasm::WsMessage::Text(string) => Self::Text(string), - ws_stream_wasm::WsMessage::Binary(data) => Self::Binary(data), - } - } -} - -impl From for GatewayMessageData { - fn from(value: String) -> Self { - Self::Text(value) - } -} - -impl GatewayMessageData { - pub fn to_text(&self) -> Result<&str, Utf8Error> { - match *self { - GatewayMessageData::Text(ref text) => Ok(text), - GatewayMessageData::Binary(ref data) => Ok(std::str::from_utf8(data)?), - } - } -} - /// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] From cefaa6545f7a96020a74514841843fdea0ee4b0e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:25:55 +0100 Subject: [PATCH 039/130] Remove duplicate code --- src/gateway/wasm/heartbeat.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/gateway/wasm/heartbeat.rs b/src/gateway/wasm/heartbeat.rs index 86d9573..6916db4 100644 --- a/src/gateway/wasm/heartbeat.rs +++ b/src/gateway/wasm/heartbeat.rs @@ -2,15 +2,3 @@ use tokio::task::JoinHandle; use ws_stream_wasm::*; use super::*; - -/// Handles sending heartbeats to the gateway in another thread -#[allow(dead_code)] // FIXME: Remove this, once used -#[derive(Debug)] -pub struct WasmHeartbeatHandler { - /// How ofter heartbeats need to be sent at a minimum - pub heartbeat_interval: Duration, - /// The send channel for the heartbeat thread - pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, -} From db80abbf432f073d5df42eb0509a2c60df30f5de Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 20:02:01 +0100 Subject: [PATCH 040/130] Make heartbeathandler shared struct --- src/gateway/default/gateway.rs | 8 +-- src/gateway/default/heartbeat.rs | 112 ++----------------------------- src/gateway/default/mod.rs | 6 +- src/gateway/mod.rs | 97 +++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 120 deletions(-) diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index 49ce89e..8ec825d 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -7,7 +7,7 @@ use crate::types::{self, WebSocketEvent}; #[derive(Debug)] pub struct DefaultGateway { events: Arc>, - heartbeat_handler: DefaultHeartbeatHandler, + heartbeat_handler: HeartbeatHandler, websocket_send: Arc< Mutex< SplitSink< @@ -28,10 +28,10 @@ impl WebSocketStream>, WebSocketStream>, DefaultGatewayHandle, - DefaultHeartbeatHandler, + HeartbeatHandler, > for DefaultGateway { - fn get_heartbeat_handler(&self) -> &DefaultHeartbeatHandler { + fn get_heartbeat_handler(&self) -> &HeartbeatHandler { &self.heartbeat_handler } @@ -95,7 +95,7 @@ impl let mut gateway = DefaultGateway { events: shared_events.clone(), - heartbeat_handler: DefaultHeartbeatHandler::new( + heartbeat_handler: HeartbeatHandler::new( Duration::from_millis(gateway_hello.heartbeat_interval), shared_websocket_send.clone(), kill_send.subscribe(), diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs index 8a6a8b5..ed39d8a 100644 --- a/src/gateway/default/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -1,22 +1,7 @@ -use crate::types; - use super::*; -/// Handles sending heartbeats to the gateway in another thread -#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used -#[derive(Debug)] -pub struct DefaultHeartbeatHandler { - /// How ofter heartbeats need to be sent at a minimum - pub heartbeat_interval: Duration, - /// The send channel for the heartbeat thread - pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, -} - -impl HeartbeatHandlerCapable>> - for DefaultHeartbeatHandler -{ +#[async_trait] +impl HeartbeatHandlerCapable>> for HeartbeatHandler { fn new( heartbeat_interval: Duration, websocket_tx: Arc< @@ -28,12 +13,12 @@ impl HeartbeatHandlerCapable>> >, >, kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> DefaultHeartbeatHandler { + ) -> HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); let handle: JoinHandle<()> = task::spawn(async move { - DefaultHeartbeatHandler::heartbeat_task( + HeartbeatHandler::heartbeat_task( websocket_tx, heartbeat_interval, receive, @@ -57,92 +42,3 @@ impl HeartbeatHandlerCapable>> self.heartbeat_interval } } - -impl DefaultHeartbeatHandler { - /// The main heartbeat task; - /// - /// Can be killed by the kill broadcast; - /// If the websocket is closed, will die out next time it tries to send a heartbeat; - pub async fn heartbeat_task( - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, - mut kill_receive: tokio::sync::broadcast::Receiver<()>, - ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); - let mut last_heartbeat_acknowledged = true; - let mut last_seq_number: Option = None; - - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - - let timeout = if last_heartbeat_acknowledged { - heartbeat_interval - } else { - // If the server hasn't acknowledged our heartbeat we should resend it - Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) - }; - - let mut should_send = false; - - tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { - should_send = true; - } - Some(communication) = receive.recv() => { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; - } - - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; - } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} - } - } - } - } - - if should_send { - trace!("GW: Sending Heartbeat.."); - - let heartbeat = types::GatewayHeartbeat { - op: GATEWAY_HEARTBEAT, - d: last_seq_number, - }; - - let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - - let send_result = websocket_tx.lock().await.send(msg).await; - if send_result.is_err() { - // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); - break; - } - - last_heartbeat_timestamp = time::Instant::now(); - last_heartbeat_acknowledged = false; - } - } - } -} diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index 8ec29e8..14e5c1e 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -5,7 +5,6 @@ pub mod heartbeat; use super::*; pub use gateway::*; pub use handle::*; -use heartbeat::*; use tokio_tungstenite::tungstenite::Message; use crate::errors::GatewayError; @@ -15,18 +14,15 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, RwLock}; use std::time::Duration; -use tokio::time::sleep_until; use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; -use log::{info, trace, warn}; +use log::{info, warn}; use tokio::net::TcpStream; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; use tokio::task; use tokio::task::JoinHandle; -use tokio::time; -use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index bdf150d..e2e3552 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -8,6 +8,8 @@ pub mod wasm; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub use default::*; pub use message::*; +use safina_timer::sleep_until; +use tokio::task::JoinHandle; #[cfg(all(target_arch = "wasm32", feature = "client"))] pub use wasm::*; @@ -22,7 +24,7 @@ use crate::types::{ use std::any::Any; use std::collections::HashMap; use std::sync::{Arc, RwLock}; -use std::time::Duration; +use std::time::{self, Duration, Instant}; use async_trait::async_trait; use futures_util::stream::SplitSink; @@ -154,7 +156,7 @@ impl GatewayEvent { pub trait GatewayCapable where R: Stream, - S: Sink, + S: Sink + Send + 'static, G: GatewayHandleCapable, H: HeartbeatHandlerCapable + Send + Sync, { @@ -441,6 +443,18 @@ where } } +/// Handles sending heartbeats to the gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used +#[derive(Debug)] +pub struct HeartbeatHandler { + /// How ofter heartbeats need to be sent at a minimum + pub heartbeat_interval: Duration, + /// The send channel for the heartbeat thread + pub send: Sender, + /// The handle of the thread + handle: JoinHandle<()>, +} + #[async_trait(?Send)] pub trait GatewayHandleCapable where @@ -541,7 +555,8 @@ where async fn close(&self); } -pub trait HeartbeatHandlerCapable> { +#[async_trait] +pub trait HeartbeatHandlerCapable + Send + 'static> { fn new( heartbeat_interval: Duration, websocket_tx: Arc>>, @@ -550,4 +565,80 @@ pub trait HeartbeatHandlerCapable> { fn get_send(&self) -> &Sender; fn get_heartbeat_interval(&self) -> Duration; + async fn heartbeat_task( + websocket_tx: Arc>>, + heartbeat_interval: Duration, + mut receive: tokio::sync::mpsc::Receiver, + mut kill_receive: tokio::sync::broadcast::Receiver<()>, + ) { + let mut last_heartbeat_timestamp: Instant = time::Instant::now(); + let mut last_heartbeat_acknowledged = true; + let mut last_seq_number: Option = None; + safina_timer::start_timer_thread(); + + loop { + if kill_receive.try_recv().is_ok() { + trace!("GW: Closing heartbeat task"); + break; + } + + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; + + let mut should_send = false; + + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; + } + Some(communication) = receive.recv() => { + // If we received a seq number update, use that as the last seq number + if communication.sequence_number.is_some() { + last_seq_number = communication.sequence_number; + } + + if let Some(op_code) = communication.op_code { + match op_code { + GATEWAY_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + GATEWAY_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} + } + } + } + } + + if should_send { + trace!("GW: Sending Heartbeat.."); + + let heartbeat = types::GatewayHeartbeat { + op: GATEWAY_HEARTBEAT, + d: last_seq_number, + }; + + let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); + + let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); + + let send_result = websocket_tx.lock().await.send(msg).await; + if send_result.is_err() { + // We couldn't send, the websocket is broken + warn!("GW: Couldnt send heartbeat, websocket seems broken"); + break; + } + + last_heartbeat_timestamp = time::Instant::now(); + last_heartbeat_acknowledged = false; + } + } + } } From 3d3c61240c4843468af536e1727055c308034c1f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 12:27:50 +0100 Subject: [PATCH 041/130] pub use --- src/gateway/wasm/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs index cdcf007..cbb0a7d 100644 --- a/src/gateway/wasm/mod.rs +++ b/src/gateway/wasm/mod.rs @@ -1,5 +1,5 @@ pub mod gateway; pub mod heartbeat; use super::*; -use gateway::*; -use heartbeat::*; +pub use gateway::*; +pub use heartbeat::*; From 17a58f6f40c3791de7816a05dad8d808c8726b9d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 15:27:22 +0100 Subject: [PATCH 042/130] Use ws_stream_wasm fork --- Cargo.lock | 3 +-- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba6758d..0f342fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,8 +2761,7 @@ dependencies = [ [[package]] name = "ws_stream_wasm" version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +source = "git+https://github.com/bitfl0wer/ws_stream_wasm?branch=feature/generic-message#48185cdb77037a7b57e244568f5c398cbebf2647" dependencies = [ "async_io_stream", "futures", diff --git a/Cargo.toml b/Cargo.toml index 5072d26..4ad1bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -ws_stream_wasm = "0.7.4" +ws_stream_wasm = { git = "https://github.com/bitfl0wer/ws_stream_wasm", branch = "feature/generic-message" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] From ce7ff49ee4a22c89a0731feb1250125cf0605a21 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:08:12 +0100 Subject: [PATCH 043/130] checkpoint --- src/api/auth/login.rs | 5 ++- src/api/auth/mod.rs | 4 +- src/gateway/default/gateway.rs | 24 ++++++------ src/gateway/default/handle.rs | 25 ++++++++++++- src/gateway/default/heartbeat.rs | 39 +++----------------- src/gateway/default/mod.rs | 21 +++++++++++ src/gateway/mod.rs | 63 ++++++++++++++++++-------------- 7 files changed, 105 insertions(+), 76 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 71d594f..b1138c0 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -4,7 +4,7 @@ use reqwest::Client; use serde_json::to_string; use crate::errors::ChorusResult; -use crate::gateway::{GatewayCapable, GatewayHandleCapable}; +use crate::gateway::{DefaultGatewayHandle, GatewayCapable, GatewayHandleCapable}; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; @@ -37,7 +37,8 @@ impl Instance { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); + let gateway: DefaultGatewayHandle = + Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); identify.token = login_result.token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 402af5c..7d03b95 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; pub use login::*; pub use register::*; -use crate::gateway::{GatewayCapable, GatewayHandleCapable}; +use crate::gateway::{DefaultGatewayHandle, GatewayCapable, GatewayHandleCapable}; use crate::{ errors::ChorusResult, gateway::DefaultGateway, @@ -26,7 +26,7 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway = DefaultGateway::get_handle(self.urls.wss.clone()) + let gateway: DefaultGatewayHandle = DefaultGateway::get_handle(self.urls.wss.clone()) .await .unwrap(); identify.token = token.clone(); diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index 8ec825d..918d9d9 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -1,4 +1,5 @@ use futures_util::StreamExt; +use tokio_tungstenite::tungstenite::Message; use super::events::Events; use super::*; @@ -25,18 +26,19 @@ pub struct DefaultGateway { #[async_trait] impl GatewayCapable< + tokio_tungstenite::tungstenite::Message, WebSocketStream>, - WebSocketStream>, - DefaultGatewayHandle, - HeartbeatHandler, > for DefaultGateway { fn get_heartbeat_handler(&self) -> &HeartbeatHandler { &self.heartbeat_handler } - #[allow(clippy::new_ret_no_self)] - async fn get_handle(websocket_url: String) -> Result { + async fn get_handle< + G: GatewayHandleCapable>>, + >( + websocket_url: String, + ) -> Result { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { @@ -112,13 +114,13 @@ impl gateway.gateway_listen_task().await; }); - Ok(DefaultGatewayHandle { - url: websocket_url.clone(), - events: shared_events, - websocket_send: shared_websocket_send.clone(), - kill_send: kill_send.clone(), + Ok(G::new( + websocket_url.clone(), + shared_events, + shared_websocket_send.clone(), + kill_send.clone(), store, - }) + )) } /// Closes the websocket connection and stops all tasks diff --git a/src/gateway/default/handle.rs b/src/gateway/default/handle.rs index e76c0f1..dc3fd4c 100644 --- a/src/gateway/default/handle.rs +++ b/src/gateway/default/handle.rs @@ -4,7 +4,7 @@ use crate::types::{self, Composite}; #[async_trait(?Send)] impl GatewayHandleCapable< - WebSocketStream>, + tokio_tungstenite::tungstenite::Message, WebSocketStream>, > for DefaultGatewayHandle { @@ -23,6 +23,29 @@ impl self.kill_send.send(()).unwrap(); self.websocket_send.lock().await.close().await.unwrap(); } + + fn new( + url: String, + events: Arc>, + websocket_send: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + kill_send: tokio::sync::broadcast::Sender<()>, + store: GatewayStore, + ) -> Self { + Self { + url, + events, + websocket_send, + kill_send, + store, + } + } } /// Represents a handle to a Gateway connection. A Gateway connection will create observable diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs index ed39d8a..e7b7129 100644 --- a/src/gateway/default/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -1,39 +1,12 @@ use super::*; #[async_trait] -impl HeartbeatHandlerCapable>> for HeartbeatHandler { - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { - let (send, receive) = tokio::sync::mpsc::channel(32); - let kill_receive = kill_rc.resubscribe(); - - let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; - }); - - Self { - heartbeat_interval, - send, - handle, - } - } - +impl + HeartbeatHandlerCapable< + tokio_tungstenite::tungstenite::Message, + WebSocketStream>, + > for HeartbeatHandler +{ fn get_send(&self) -> &Sender { &self.send } diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index 14e5c1e..f90f97d 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -26,6 +26,27 @@ use tokio::task::JoinHandle; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; +impl crate::gateway::MessageCapable for tokio_tungstenite::tungstenite::Message { + fn as_string(&self) -> Option { + match self { + Message::Text(text) => Some(text.clone()), + _ => None, + } + } + + fn is_empty(&self) -> bool { + todo!() + } + + fn is_error(&self) -> bool { + todo!() + } + + fn as_bytes(&self) -> Option> { + todo!() + } +} + #[cfg(test)] mod test { use crate::types; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index e2e3552..071e52f 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -29,11 +29,10 @@ use std::time::{self, Duration, Instant}; use async_trait::async_trait; use futures_util::stream::SplitSink; use futures_util::Sink; -use futures_util::{SinkExt, Stream}; +use futures_util::SinkExt; use log::{info, trace, warn}; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; -use tokio_tungstenite::tungstenite::Message; pub type GatewayStore = Arc>>>>; @@ -88,6 +87,13 @@ const GATEWAY_CALL_SYNC: u8 = 13; /// See [types::LazyRequest] const GATEWAY_LAZY_REQUEST: u8 = 14; +pub trait MessageCapable { + fn as_string(&self) -> Option; + fn as_bytes(&self) -> Option>; + fn is_empty(&self) -> bool; + fn is_error(&self) -> bool; +} + pub type ObservableObject = dyn Send + Sync + Any; /// Used for communications between the heartbeat and gateway thread. @@ -153,22 +159,22 @@ impl GatewayEvent { #[allow(clippy::type_complexity)] #[async_trait] -pub trait GatewayCapable +pub trait GatewayCapable where - R: Stream, - S: Sink + Send + 'static, - G: GatewayHandleCapable, - H: HeartbeatHandlerCapable + Send + Sync, + T: MessageCapable + Send + 'static, + S: Sink + Send, { fn get_events(&self) -> Arc>; - fn get_websocket_send(&self) -> Arc>>; + fn get_websocket_send(&self) -> Arc>>; fn get_store(&self) -> GatewayStore; fn get_url(&self) -> String; - fn get_heartbeat_handler(&self) -> &H; + fn get_heartbeat_handler(&self) -> &HeartbeatHandler; /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] /// /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - async fn get_handle(websocket_url: String) -> Result; + async fn get_handle>( + websocket_url: String, + ) -> Result; async fn close(&mut self); /// This handles a message as a websocket event and updates its events along with the events' observers async fn handle_message(&mut self, msg: GatewayMessage) { @@ -456,27 +462,35 @@ pub struct HeartbeatHandler { } #[async_trait(?Send)] -pub trait GatewayHandleCapable +pub trait GatewayHandleCapable where - R: Stream, - S: Sink, + T: MessageCapable + Send + 'static, + S: Sink, { + fn new( + url: String, + events: Arc>, + websocket_send: Arc>>, + kill_send: tokio::sync::broadcast::Sender<()>, + store: GatewayStore, + ) -> Self; + /// Sends json to the gateway with an opcode async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value); /// Observes an Item ``, which will update itself, if new information about this /// item arrives on the corresponding Gateway Thread - async fn observe + Send + Sync>( + async fn observe + Send + Sync>( &self, - object: Arc>, - ) -> Arc>; + object: Arc>, + ) -> Arc>; /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` /// with all of its observable fields being observed. - async fn observe_and_into_inner>( + async fn observe_and_into_inner>( &self, - object: Arc>, - ) -> T { + object: Arc>, + ) -> U { let channel = self.observe(object.clone()).await; let object = channel.read().unwrap().clone(); object @@ -556,17 +570,12 @@ where } #[async_trait] -pub trait HeartbeatHandlerCapable + Send + 'static> { - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc>>, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> Self; - +pub trait HeartbeatHandlerCapable> { fn get_send(&self) -> &Sender; + fn as_arc_mutex(&self) -> Arc>; fn get_heartbeat_interval(&self) -> Duration; async fn heartbeat_task( - websocket_tx: Arc>>, + websocket_tx: Arc>>, heartbeat_interval: Duration, mut receive: tokio::sync::mpsc::Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, From 16bdd68d98156921b8d9648a98b01a42671e1aa5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:39:01 +0100 Subject: [PATCH 044/130] Fixed most errors, simplified new generic traits --- examples/gateway_observers.rs | 5 +- examples/gateway_simple.rs | 3 +- examples/instance.rs | 2 +- examples/login.rs | 2 +- src/gateway/default/heartbeat.rs | 33 +++++++ src/gateway/mod.rs | 163 ++++++++++++++++--------------- tests/gateway.rs | 6 +- 7 files changed, 129 insertions(+), 85 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 511c568..921a048 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; -use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; +use chorus::gateway::GatewayCapable; +use chorus::GatewayHandle; use chorus::{ self, gateway::Observer, @@ -32,7 +33,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection - let gateway = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); + let gateway: GatewayHandle = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index e46d122..331fc6c 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,6 +1,7 @@ use std::time::Duration; use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; +use chorus::GatewayHandle; use chorus::{self, types::GatewayIdentifyPayload, Gateway}; use tokio::time::sleep; @@ -11,7 +12,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); + let gateway: GatewayHandle = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated diff --git a/examples/instance.rs b/examples/instance.rs index 337482b..d2a042f 100644 --- a/examples/instance.rs +++ b/examples/instance.rs @@ -1,7 +1,7 @@ use chorus::instance::Instance; use chorus::UrlBundle; -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { let bundle = UrlBundle::new( "https://example.com/api".to_string(), diff --git a/examples/login.rs b/examples/login.rs index 4595a06..b06eade 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -2,7 +2,7 @@ use chorus::instance::Instance; use chorus::types::LoginSchema; use chorus::UrlBundle; -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { let bundle = UrlBundle::new( "https://example.com/api".to_string(), diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs index e7b7129..4b9fff7 100644 --- a/src/gateway/default/heartbeat.rs +++ b/src/gateway/default/heartbeat.rs @@ -1,5 +1,6 @@ use super::*; +// TODO: Make me not a trait and delete this file #[async_trait] impl HeartbeatHandlerCapable< @@ -14,4 +15,36 @@ impl fn get_heartbeat_interval(&self) -> Duration { self.heartbeat_interval } + + fn new( + heartbeat_interval: Duration, + websocket_tx: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> HeartbeatHandler { + let (send, receive) = tokio::sync::mpsc::channel(32); + let kill_receive = kill_rc.resubscribe(); + + let handle: JoinHandle<()> = task::spawn(async move { + HeartbeatHandler::heartbeat_task( + websocket_tx, + heartbeat_interval, + receive, + kill_receive, + ) + .await; + }); + + Self { + heartbeat_interval, + send, + handle, + } + } } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 071e52f..3d54137 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -87,7 +87,7 @@ const GATEWAY_CALL_SYNC: u8 = 13; /// See [types::LazyRequest] const GATEWAY_LAZY_REQUEST: u8 = 14; -pub trait MessageCapable { +pub trait MessageCapable: From { fn as_string(&self) -> Option; fn as_bytes(&self) -> Option>; fn is_empty(&self) -> bool; @@ -461,6 +461,85 @@ pub struct HeartbeatHandler { handle: JoinHandle<()>, } +impl HeartbeatHandler { + pub async fn heartbeat_task + Send>( + websocket_tx: Arc>>, + heartbeat_interval: Duration, + mut receive: tokio::sync::mpsc::Receiver, + mut kill_receive: tokio::sync::broadcast::Receiver<()>, + ) { + let mut last_heartbeat_timestamp: Instant = time::Instant::now(); + let mut last_heartbeat_acknowledged = true; + let mut last_seq_number: Option = None; + safina_timer::start_timer_thread(); + + loop { + if kill_receive.try_recv().is_ok() { + trace!("GW: Closing heartbeat task"); + break; + } + + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; + + let mut should_send = false; + + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; + } + Some(communication) = receive.recv() => { + // If we received a seq number update, use that as the last seq number + if communication.sequence_number.is_some() { + last_seq_number = communication.sequence_number; + } + + if let Some(op_code) = communication.op_code { + match op_code { + GATEWAY_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + GATEWAY_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} + } + } + } + } + + if should_send { + trace!("GW: Sending Heartbeat.."); + + let heartbeat = types::GatewayHeartbeat { + op: GATEWAY_HEARTBEAT, + d: last_seq_number, + }; + + let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); + + let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); + + let send_result = websocket_tx.lock().await.send(msg.into()).await; + if send_result.is_err() { + // We couldn't send, the websocket is broken + warn!("GW: Couldnt send heartbeat, websocket seems broken"); + break; + } + + last_heartbeat_timestamp = time::Instant::now(); + last_heartbeat_acknowledged = false; + } + } + } +} + #[async_trait(?Send)] pub trait GatewayHandleCapable where @@ -570,84 +649,14 @@ where } #[async_trait] +// TODO: Make me not a trait!! pub trait HeartbeatHandlerCapable> { fn get_send(&self) -> &Sender; - fn as_arc_mutex(&self) -> Arc>; fn get_heartbeat_interval(&self) -> Duration; - async fn heartbeat_task( - websocket_tx: Arc>>, + #[allow(clippy::new_ret_no_self)] + fn new( heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, - mut kill_receive: tokio::sync::broadcast::Receiver<()>, - ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); - let mut last_heartbeat_acknowledged = true; - let mut last_seq_number: Option = None; - safina_timer::start_timer_thread(); - - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - - let timeout = if last_heartbeat_acknowledged { - heartbeat_interval - } else { - // If the server hasn't acknowledged our heartbeat we should resend it - Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) - }; - - let mut should_send = false; - - tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { - should_send = true; - } - Some(communication) = receive.recv() => { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; - } - - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; - } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} - } - } - } - } - - if should_send { - trace!("GW: Sending Heartbeat.."); - - let heartbeat = types::GatewayHeartbeat { - op: GATEWAY_HEARTBEAT, - d: last_seq_number, - }; - - let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - - let send_result = websocket_tx.lock().await.send(msg).await; - if send_result.is_err() { - // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); - break; - } - - last_heartbeat_timestamp = time::Instant::now(); - last_heartbeat_acknowledged = false; - } - } - } + websocket_tx: Arc>>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> HeartbeatHandler; } diff --git a/tests/gateway.rs b/tests/gateway.rs index 34c841f..f71c1fc 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,15 +2,15 @@ mod common; use std::sync::{Arc, RwLock}; -use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +use chorus::{gateway::*, GatewayHandle}; #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { let bundle = common::setup().await; - DefaultGateway::get_handle(bundle.urls.wss.clone()) + let _: GatewayHandle = DefaultGateway::get_handle(bundle.urls.wss.clone()) .await .unwrap(); common::teardown(bundle).await @@ -21,7 +21,7 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = DefaultGateway::get_handle(bundle.urls.wss.clone()) + let gateway: GatewayHandle = DefaultGateway::get_handle(bundle.urls.wss.clone()) .await .unwrap(); From 9507d489bf0ec37ac080da0afcfbdf9614afd15c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:41:11 +0100 Subject: [PATCH 045/130] Import trait --- examples/gateway_observers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 921a048..1ea1761 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use chorus::gateway::GatewayCapable; +use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; use chorus::GatewayHandle; use chorus::{ self, From 964d879e8fd8710d6f9f91c12282f3b3b14803af Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:50:47 +0100 Subject: [PATCH 046/130] Remove unused code --- src/gateway/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 3d54137..3add6b1 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -157,7 +157,6 @@ impl GatewayEvent { } } -#[allow(clippy::type_complexity)] #[async_trait] pub trait GatewayCapable where From 862a84605564a7ab6685a5f47776e56dda4cfb08 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:07:56 +0100 Subject: [PATCH 047/130] Rename `get_handle` to `spawn` --- examples/gateway_observers.rs | 2 +- examples/gateway_simple.rs | 2 +- src/api/auth/login.rs | 3 +-- src/api/auth/mod.rs | 5 ++--- src/api/auth/register.rs | 2 +- src/gateway/default/gateway.rs | 4 +--- src/gateway/mod.rs | 5 ++--- src/instance.rs | 2 +- tests/common/mod.rs | 2 +- tests/gateway.rs | 4 ++-- 10 files changed, 13 insertions(+), 18 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 1ea1761..7d49a1f 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -33,7 +33,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection - let gateway: GatewayHandle = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); + let gateway: GatewayHandle = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 331fc6c..56c557a 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -12,7 +12,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway: GatewayHandle = Gateway::get_handle(websocket_url_spacebar).await.unwrap(); + let gateway: GatewayHandle = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index b1138c0..5eddfa3 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -37,8 +37,7 @@ impl Instance { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } let mut identify = GatewayIdentifyPayload::common(); - let gateway: DefaultGatewayHandle = - Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); + let gateway: DefaultGatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = login_result.token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 7d03b95..a1490fc 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -26,9 +26,8 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway: DefaultGatewayHandle = DefaultGateway::get_handle(self.urls.wss.clone()) - .await - .unwrap(); + let gateway: DefaultGatewayHandle = + DefaultGateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index f416124..2ea6e7d 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -46,7 +46,7 @@ impl Instance { let user_object = self.get_user(token.clone(), None).await.unwrap(); let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), &mut self).await?; let mut identify = GatewayIdentifyPayload::common(); - let gateway: GatewayHandle = Gateway::get_handle(self.urls.wss.clone()).await.unwrap(); + let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index 918d9d9..5dd5db6 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -34,9 +34,7 @@ impl &self.heartbeat_handler } - async fn get_handle< - G: GatewayHandleCapable>>, - >( + async fn spawn>>>( websocket_url: String, ) -> Result { let mut roots = rustls::RootCertStore::empty(); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 3add6b1..96e2155 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -171,9 +171,8 @@ where /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] /// /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - async fn get_handle>( - websocket_url: String, - ) -> Result; + async fn spawn>(websocket_url: String) + -> Result; async fn close(&mut self); /// This handles a message as a websocket event and updates its events along with the events' observers async fn handle_message(&mut self, msg: GatewayMessage) { diff --git a/src/instance.rs b/src/instance.rs index 4acfd26..8c462a6 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -135,7 +135,7 @@ impl ChorusUser { let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); // Dummy gateway object - let gateway = Gateway::get_handle(wss_url).await.unwrap(); + let gateway = Gateway::spawn(wss_url).await.unwrap(); ChorusUser { token, belongs_to: instance.clone(), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 95c2de3..76f20c7 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -43,7 +43,7 @@ impl TestBundle { limits: self.user.limits.clone(), settings: self.user.settings.clone(), object: self.user.object.clone(), - gateway: DefaultGateway::get_handle(self.instance.urls.wss.clone()) + gateway: DefaultGateway::spawn(self.instance.urls.wss.clone()) .await .unwrap(), } diff --git a/tests/gateway.rs b/tests/gateway.rs index f71c1fc..2303707 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -10,7 +10,7 @@ use chorus::{gateway::*, GatewayHandle}; async fn test_gateway_establish() { let bundle = common::setup().await; - let _: GatewayHandle = DefaultGateway::get_handle(bundle.urls.wss.clone()) + let _: GatewayHandle = DefaultGateway::spawn(bundle.urls.wss.clone()) .await .unwrap(); common::teardown(bundle).await @@ -21,7 +21,7 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway: GatewayHandle = DefaultGateway::get_handle(bundle.urls.wss.clone()) + let gateway: GatewayHandle = DefaultGateway::spawn(bundle.urls.wss.clone()) .await .unwrap(); From 81455afb7c02d5cf22d60f5eea836244aa75314b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:13:26 +0100 Subject: [PATCH 048/130] Use upstream ws_stream_wasm --- Cargo.lock | 3 ++- Cargo.toml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f342fb..ba6758d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,7 +2761,8 @@ dependencies = [ [[package]] name = "ws_stream_wasm" version = "0.7.4" -source = "git+https://github.com/bitfl0wer/ws_stream_wasm?branch=feature/generic-message#48185cdb77037a7b57e244568f5c398cbebf2647" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" dependencies = [ "async_io_stream", "futures", diff --git a/Cargo.toml b/Cargo.toml index 4ad1bcb..b09926d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,7 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -ws_stream_wasm = { git = "https://github.com/bitfl0wer/ws_stream_wasm", branch = "feature/generic-message" } - +ws_stream_wasm = "0.7.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" From 29efae385e9f42c9ba9fdb1b3f22da2298fbd1a2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:18:55 +0100 Subject: [PATCH 049/130] Apparently, the conditional dependency now works again. lol --- Cargo.toml | 2 +- src/gateway/wasm/gateway.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b09926d..433ca85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -ws_stream_wasm = "0.7.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" @@ -69,6 +68,7 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } tokio-tungstenite = { version = "0.20.1", default-features = false } +ws_stream_wasm = "0.7.4" [dev-dependencies] lazy_static = "1.4.0" diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index 5a54efb..560d58f 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -1 +1,6 @@ +pub use super::*; use ws_stream_wasm::*; +#[derive(Debug)] +pub struct WasmGateway { + events: Arc>, +} From 287b72c99c834be4f03ac50661f1396dc806c630 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:28 +0100 Subject: [PATCH 050/130] Implement MessageCapable trait methods in gateway default module --- src/gateway/default/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index f90f97d..db94cd6 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -35,15 +35,18 @@ impl crate::gateway::MessageCapable for tokio_tungstenite::tungstenite::Message } fn is_empty(&self) -> bool { - todo!() - } - - fn is_error(&self) -> bool { - todo!() + match self { + Message::Text(text) => text.is_empty(), + Message::Binary(bytes) => bytes.is_empty(), + _ => false, + } } fn as_bytes(&self) -> Option> { - todo!() + match self { + Message::Binary(bytes) => Some(bytes.clone()), + _ => None, + } } } From f1e3058346ac911f696ba8bc10bfbba130a4b3b0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:34 +0100 Subject: [PATCH 051/130] Update Gateway types based on target architecture and feature --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5bb866b..510a727 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,11 +19,21 @@ compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type Gateway = DefaultGateway; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Gateway = WasmGateway; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type GatewayHandle = DefaultGatewayHandle; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type GatewayHandle = WasmGatewayHandle; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] use gateway::DefaultGateway; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] use gateway::DefaultGatewayHandle; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +use gateway::WasmGateway; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +use gateway::WasmGatewayHandle; use url::{ParseError, Url}; #[cfg(feature = "client")] From 39e61a73e98087da72cb4cd15ed65c3a2e1f5c53 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:39 +0100 Subject: [PATCH 052/130] Remove is_error method from MessageCapable trait --- src/gateway/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 96e2155..9f998d6 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -91,7 +91,6 @@ pub trait MessageCapable: From { fn as_string(&self) -> Option; fn as_bytes(&self) -> Option>; fn is_empty(&self) -> bool; - fn is_error(&self) -> bool; } pub type ObservableObject = dyn Send + Sync + Any; From 6ae715988d0e08dbe3f0db491f5da89427eee905 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:45 +0100 Subject: [PATCH 053/130] Refactor WasmGateway struct and import statements --- src/gateway/wasm/gateway.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index 560d58f..8b30e7d 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -1,6 +1,16 @@ -pub use super::*; +use std::sync::Arc; + +use super::events::Events; +use super::*; use ws_stream_wasm::*; + +use crate::types::{self, WebSocketEvent}; #[derive(Debug)] pub struct WasmGateway { events: Arc>, + heartbeat_handler: HeartbeatHandler, + websocket_send: Arc>>, + websocket_receive: SplitStream, + store: GatewayStore, + url: String, } From a0d14ceaf9910f501ac7dc62b5bae93bbbec325f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:50 +0100 Subject: [PATCH 054/130] Add MessageCapable trait implementation for WsMessage --- src/gateway/wasm/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs index cbb0a7d..aaab48f 100644 --- a/src/gateway/wasm/mod.rs +++ b/src/gateway/wasm/mod.rs @@ -3,3 +3,28 @@ pub mod heartbeat; use super::*; pub use gateway::*; pub use heartbeat::*; +use ws_stream_wasm::WsMessage; + +impl crate::gateway::MessageCapable for WsMessage { + fn as_string(&self) -> Option { + match self { + WsMessage::Text(text) => Some(text.clone()), + _ => None, + } + } + + fn as_bytes(&self) -> Option> { + match self { + WsMessage::Binary(bytes) => Some(bytes.clone()), + _ => None, + } + } + + fn is_empty(&self) -> bool { + match self { + WsMessage::Text(text) => text.is_empty(), + WsMessage::Binary(bytes) => bytes.is_empty(), + _ => false, + } + } +} From 5aeba447af1bfcd9860cd621296638fa24380a24 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 21:42:48 +0100 Subject: [PATCH 055/130] Remove trait bound: From tungstenite::Message --- src/gateway/default/mod.rs | 4 ++++ src/gateway/mod.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index db94cd6..ae9db05 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -48,6 +48,10 @@ impl crate::gateway::MessageCapable for tokio_tungstenite::tungstenite::Message _ => None, } } + + fn from_str(s: &str) -> Self { + Message::Text(s.to_string()) + } } #[cfg(test)] diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 9f998d6..d9dc95c 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -87,10 +87,11 @@ const GATEWAY_CALL_SYNC: u8 = 13; /// See [types::LazyRequest] const GATEWAY_LAZY_REQUEST: u8 = 14; -pub trait MessageCapable: From { +pub trait MessageCapable { fn as_string(&self) -> Option; fn as_bytes(&self) -> Option>; fn is_empty(&self) -> bool; + fn from_str(s: &str) -> Self; } pub type ObservableObject = dyn Send + Sync + Any; @@ -523,7 +524,11 @@ impl HeartbeatHandler { let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - let send_result = websocket_tx.lock().await.send(msg.into()).await; + let send_result = websocket_tx + .lock() + .await + .send(MessageCapable::from_str(msg.to_string().as_str())) + .await; if send_result.is_err() { // We couldn't send, the websocket is broken warn!("GW: Couldnt send heartbeat, websocket seems broken"); From 9ae07a15d70c790a962acbec87e803a522e2e043 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 01:31:04 +0100 Subject: [PATCH 056/130] start implementing wasm gateway --- Cargo.lock | 1 + Cargo.toml | 2 ++ src/gateway/wasm/gateway.rs | 39 +++++++++++++++++++++++++++++++++++++ src/gateway/wasm/handle.rs | 34 ++++++++++++++++++++++++++++++++ src/gateway/wasm/mod.rs | 6 ++++++ 5 files changed, 82 insertions(+) create mode 100644 src/gateway/wasm/handle.rs diff --git a/Cargo.lock b/Cargo.lock index ba6758d..f7379c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,6 +205,7 @@ dependencies = [ "lazy_static", "log", "native-tls", + "pharos", "poem", "rand", "regex", diff --git a/Cargo.toml b/Cargo.toml index 433ca85..0907694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,8 @@ hostname = "0.3.1" getrandom = { version = "0.2.11", features = ["js"] } tokio-tungstenite = { version = "0.20.1", default-features = false } ws_stream_wasm = "0.7.4" +pharos = "0.5.3" + [dev-dependencies] lazy_static = "1.4.0" diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index 8b30e7d..f3bd3d7 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use super::events::Events; use super::*; +use pharos::*; use ws_stream_wasm::*; use crate::types::{self, WebSocketEvent}; @@ -14,3 +15,41 @@ pub struct WasmGateway { store: GatewayStore, url: String, } + +#[async_trait] +impl GatewayCapable for WasmGateway { + fn get_events(&self) -> Arc> { + self.events.clone() + } + + fn get_websocket_send(&self) -> Arc>> { + self.websocket_send.clone() + } + + fn get_store(&self) -> GatewayStore { + self.store.clone() + } + + fn get_url(&self) -> String { + self.url.clone() + } + + async fn spawn>( + websocket_url: String, + ) -> Result { + let (mut websocket_stream, _) = match WsMeta::connect(websocket_url, None).await { + Ok(ws) => Ok(ws), + Err(e) => Err(GatewayError::CannotConnect { + error: e.to_string(), + }), + }?; + + let mut event = match websocket_stream + .observe(ObserveConfig::channel(self, Channel::Unbounded)) + .await + { + Ok(ok) => Ok(ok), + Err(e) => Err(GatewayError::CannotConnect { error: e }), + }?; + } +} diff --git a/src/gateway/wasm/handle.rs b/src/gateway/wasm/handle.rs new file mode 100644 index 0000000..c9ad14d --- /dev/null +++ b/src/gateway/wasm/handle.rs @@ -0,0 +1,34 @@ +use super::*; +use std::sync::Arc; + +use crate::gateway::GatewayHandleCapable; +use ws_stream_wasm::*; + +#[derive(Debug, Clone)] +pub struct WasmGatewayHandle { + pub url: String, + pub events: Arc>, + pub websocket_send: Arc>>, + /// Tells gateway tasks to close + pub(super) kill_send: tokio::sync::broadcast::Sender<()>, + pub(crate) store: GatewayStore, +} + +#[async_trait] +impl GatewayHandleCapable for WasmGatewayHandle { + fn new( + url: String, + events: Arc>, + websocket_send: Arc>>, + kill_send: tokio::sync::broadcast::Sender<()>, + store: GatewayStore, + ) -> Self { + Self { + url, + events, + websocket_send, + kill_send, + store, + } + } +} diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs index aaab48f..6cb87f2 100644 --- a/src/gateway/wasm/mod.rs +++ b/src/gateway/wasm/mod.rs @@ -1,7 +1,9 @@ pub mod gateway; +pub mod handle; pub mod heartbeat; use super::*; pub use gateway::*; +pub use handle::*; pub use heartbeat::*; use ws_stream_wasm::WsMessage; @@ -27,4 +29,8 @@ impl crate::gateway::MessageCapable for WsMessage { _ => false, } } + + fn from_str(s: &str) -> Self { + WsMessage::Text(s.to_string()) + } } From 565a0cd74526643dbaa940944979786b84f6a074 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:51:07 +0100 Subject: [PATCH 057/130] Impl spawn() for wasm gateway --- src/gateway/wasm/gateway.rs | 75 ++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index f3bd3d7..bb4573c 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -1,11 +1,14 @@ use std::sync::Arc; +use std::u8; use super::events::Events; use super::*; -use pharos::*; +use futures_util::StreamExt; +use tokio::task; +use tokio_stream::StreamExt; use ws_stream_wasm::*; -use crate::types::{self, WebSocketEvent}; +use crate::types::{self, GatewayReceivePayload, WebSocketEvent}; #[derive(Debug)] pub struct WasmGateway { events: Arc>, @@ -37,19 +40,73 @@ impl GatewayCapable for WasmGateway { async fn spawn>( websocket_url: String, ) -> Result { - let (mut websocket_stream, _) = match WsMeta::connect(websocket_url, None).await { + let (_, mut websocket_stream) = match WsMeta::connect(websocket_url.clone(), None).await { Ok(ws) => Ok(ws), Err(e) => Err(GatewayError::CannotConnect { error: e.to_string(), }), }?; - let mut event = match websocket_stream - .observe(ObserveConfig::channel(self, Channel::Unbounded)) - .await - { - Ok(ok) => Ok(ok), - Err(e) => Err(GatewayError::CannotConnect { error: e }), + let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); + let (websocket_send, mut websocket_receive) = websocket_stream.split(); + let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); + + let msg = match websocket_receive.next().await { + Some(msg) => match msg { + WsMessage::Text(text) => Ok(text), + WsMessage::Binary(vec) => Err(GatewayError::NonHelloOnInitiate { + opcode: vec.into_iter().next().unwrap_or(u8::MIN), + }), + }, + None => Err(GatewayError::CannotConnect { + error: "No 'Hello' message received!".to_string(), + }), }?; + + let payload: GatewayReceivePayload = match serde_json::from_str(msg.as_str()) { + Ok(msg) => Ok(msg), + Err(_) => Err(GatewayError::Decode), + }?; + if payload.op_code != GATEWAY_HELLO { + return Err(GatewayError::NonHelloOnInitiate { + opcode: payload.op_code, + }); + }; + + info!("GW: Received Hello"); + + let gateway_hello: types::HelloData = + serde_json::from_str(payload.event_data.unwrap().get()).unwrap(); + + let events = Events::default(); + let shared_events: Arc> = Arc::new(Mutex::new(events)); + let store: GatewayStore = Arc::new(Mutex::new(HashMap::new())); + + let mut gateway = WasmGateway { + events: shared_events.clone(), + heartbeat_handler: todo!(), + websocket_send: shared_websocket_send.clone(), + websocket_receive, + store: store.clone(), + url: websocket_url.clone(), + }; + + task::spawn_local(async move { + gateway.gateway_listen_task().await; + }); + + Ok(G::new( + websocket_url.clone(), + shared_events.clone(), + shared_websocket_send.clone(), + kill_send.clone(), + store, + )) + } +} + +impl WasmGateway { + async fn gateway_listen_task(&mut self) { + todo!() } } From 9f45094bd93d084739a5c99ee1279502b37dfaa9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:51:13 +0100 Subject: [PATCH 058/130] Add todos --- src/gateway/default/gateway.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index 5dd5db6..a8bb69c 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -73,6 +73,7 @@ impl // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread + // TODO: We likely should not unwrap here. let msg = websocket_receive.next().await.unwrap().unwrap(); let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(msg.to_text().unwrap()).unwrap(); @@ -89,7 +90,7 @@ impl serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); let events = Events::default(); - let shared_events = Arc::new(Mutex::new(events)); + let shared_events: Arc> = Arc::new(Mutex::new(events)); let store = Arc::new(Mutex::new(HashMap::new())); From 926a9fc90ec641384aa62912c8df224a2829784d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:52:11 +0100 Subject: [PATCH 059/130] TEMP: Compile wasm dependencies&code per default TODO: Check todos before merging into dev --- Cargo.toml | 3 +++ src/gateway/mod.rs | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0907694..72028c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,9 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" +# TODO: Remove the below 2 imports for production! +ws_stream_wasm = "0.7.4" +pharos = "0.5.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index d9dc95c..470c3e5 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -2,7 +2,8 @@ pub mod default; pub mod events; pub mod message; -#[cfg(all(target_arch = "wasm32", feature = "client"))] +// TODO: Uncomment for Prod! +// #[cfg(all(target_arch = "wasm32", feature = "client"))] pub mod wasm; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] @@ -10,7 +11,8 @@ pub use default::*; pub use message::*; use safina_timer::sleep_until; use tokio::task::JoinHandle; -#[cfg(all(target_arch = "wasm32", feature = "client"))] +// TODO: Uncomment for Prod! +// #[cfg(all(target_arch = "wasm32", feature = "client"))] pub use wasm::*; use self::events::Events; @@ -171,6 +173,7 @@ where /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] /// /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation + /// TODO: Give spawn a default trait impl, avoid code duplication async fn spawn>(websocket_url: String) -> Result; async fn close(&mut self); From 50cd93aae15c4c8c0a67f3efcf95ce4a841c77ed Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:42:05 +0100 Subject: [PATCH 060/130] Add kill send to WasmGateway --- src/gateway/wasm/gateway.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index bb4573c..cdbb991 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -3,18 +3,19 @@ use std::u8; use super::events::Events; use super::*; +use futures_util::stream::SplitStream; use futures_util::StreamExt; use tokio::task; -use tokio_stream::StreamExt; use ws_stream_wasm::*; -use crate::types::{self, GatewayReceivePayload, WebSocketEvent}; +use crate::types::{self, GatewayReceivePayload}; #[derive(Debug)] pub struct WasmGateway { events: Arc>, heartbeat_handler: HeartbeatHandler, websocket_send: Arc>>, websocket_receive: SplitStream, + kill_send: tokio::sync::broadcast::Sender<()>, store: GatewayStore, url: String, } @@ -84,10 +85,15 @@ impl GatewayCapable for WasmGateway { let mut gateway = WasmGateway { events: shared_events.clone(), - heartbeat_handler: todo!(), + heartbeat_handler: HeartbeatHandler::new( + Duration::from_millis(gateway_hello.heartbeat_interval), + shared_websocket_send.clone(), + kill_send.subscribe(), + ), websocket_send: shared_websocket_send.clone(), websocket_receive, store: store.clone(), + kill_send: kill_send.clone(), url: websocket_url.clone(), }; @@ -103,6 +109,15 @@ impl GatewayCapable for WasmGateway { store, )) } + + fn get_heartbeat_handler(&self) -> &HeartbeatHandler { + &self.heartbeat_handler + } + + async fn close(&mut self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } } impl WasmGateway { From b3ebdd69fcc0d5bf4299d737bc334dba68985c6f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:42:48 +0100 Subject: [PATCH 061/130] Add TODO comment --- src/gateway/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 470c3e5..28e49a8 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -659,6 +659,7 @@ pub trait HeartbeatHandlerCapable fn get_send(&self) -> &Sender; fn get_heartbeat_interval(&self) -> Duration; #[allow(clippy::new_ret_no_self)] + // TODO: new() has duplicated code in wasm and default impl. Can be fixed, if this is not a trait fn new( heartbeat_interval: Duration, websocket_tx: Arc>>, From f4ae80fee98c3e5926732bc25538705d4cce5ccd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:43:21 +0100 Subject: [PATCH 062/130] temp: impl heartbeathandlercapable --- src/gateway/wasm/heartbeat.rs | 38 ++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/gateway/wasm/heartbeat.rs b/src/gateway/wasm/heartbeat.rs index 6916db4..80f863e 100644 --- a/src/gateway/wasm/heartbeat.rs +++ b/src/gateway/wasm/heartbeat.rs @@ -1,4 +1,40 @@ -use tokio::task::JoinHandle; +use tokio::task::{self, JoinHandle}; use ws_stream_wasm::*; use super::*; + +#[async_trait] +impl HeartbeatHandlerCapable for HeartbeatHandler { + fn get_send(&self) -> &Sender { + &self.send + } + + fn get_heartbeat_interval(&self) -> Duration { + self.heartbeat_interval + } + + fn new( + heartbeat_interval: Duration, + websocket_tx: Arc>>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> HeartbeatHandler { + let (send, receive) = tokio::sync::mpsc::channel(32); + let kill_receive = kill_rc.resubscribe(); + + let handle: JoinHandle<()> = task::spawn(async move { + HeartbeatHandler::heartbeat_task( + websocket_tx, + heartbeat_interval, + receive, + kill_receive, + ) + .await; + }); + + Self { + heartbeat_interval, + send, + handle, + } + } +} From 70812c529a61e00c408ea74cda32e3691d584513 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:44:38 +0100 Subject: [PATCH 063/130] Move Heartbeathandler code together --- src/gateway/mod.rs | 189 ++++++++++++++++++++++----------------------- 1 file changed, 94 insertions(+), 95 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 28e49a8..44c8ee2 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -450,101 +450,6 @@ where } } -/// Handles sending heartbeats to the gateway in another thread -#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used -#[derive(Debug)] -pub struct HeartbeatHandler { - /// How ofter heartbeats need to be sent at a minimum - pub heartbeat_interval: Duration, - /// The send channel for the heartbeat thread - pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, -} - -impl HeartbeatHandler { - pub async fn heartbeat_task + Send>( - websocket_tx: Arc>>, - heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, - mut kill_receive: tokio::sync::broadcast::Receiver<()>, - ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); - let mut last_heartbeat_acknowledged = true; - let mut last_seq_number: Option = None; - safina_timer::start_timer_thread(); - - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - - let timeout = if last_heartbeat_acknowledged { - heartbeat_interval - } else { - // If the server hasn't acknowledged our heartbeat we should resend it - Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) - }; - - let mut should_send = false; - - tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { - should_send = true; - } - Some(communication) = receive.recv() => { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; - } - - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; - } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} - } - } - } - } - - if should_send { - trace!("GW: Sending Heartbeat.."); - - let heartbeat = types::GatewayHeartbeat { - op: GATEWAY_HEARTBEAT, - d: last_seq_number, - }; - - let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - - let send_result = websocket_tx - .lock() - .await - .send(MessageCapable::from_str(msg.to_string().as_str())) - .await; - if send_result.is_err() { - // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); - break; - } - - last_heartbeat_timestamp = time::Instant::now(); - last_heartbeat_acknowledged = false; - } - } - } -} - #[async_trait(?Send)] pub trait GatewayHandleCapable where @@ -653,6 +558,100 @@ where async fn close(&self); } +/// Handles sending heartbeats to the gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used +#[derive(Debug)] +pub struct HeartbeatHandler { + /// How ofter heartbeats need to be sent at a minimum + pub heartbeat_interval: Duration, + /// The send channel for the heartbeat thread + pub send: Sender, + /// The handle of the thread + handle: JoinHandle<()>, +} + +impl HeartbeatHandler { + pub async fn heartbeat_task + Send>( + websocket_tx: Arc>>, + heartbeat_interval: Duration, + mut receive: tokio::sync::mpsc::Receiver, + mut kill_receive: tokio::sync::broadcast::Receiver<()>, + ) { + let mut last_heartbeat_timestamp: Instant = time::Instant::now(); + let mut last_heartbeat_acknowledged = true; + let mut last_seq_number: Option = None; + safina_timer::start_timer_thread(); + + loop { + if kill_receive.try_recv().is_ok() { + trace!("GW: Closing heartbeat task"); + break; + } + + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; + + let mut should_send = false; + + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; + } + Some(communication) = receive.recv() => { + // If we received a seq number update, use that as the last seq number + if communication.sequence_number.is_some() { + last_seq_number = communication.sequence_number; + } + + if let Some(op_code) = communication.op_code { + match op_code { + GATEWAY_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + GATEWAY_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} + } + } + } + } + + if should_send { + trace!("GW: Sending Heartbeat.."); + + let heartbeat = types::GatewayHeartbeat { + op: GATEWAY_HEARTBEAT, + d: last_seq_number, + }; + + let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); + + let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); + + let send_result = websocket_tx + .lock() + .await + .send(MessageCapable::from_str(msg.to_string().as_str())) + .await; + if send_result.is_err() { + // We couldn't send, the websocket is broken + warn!("GW: Couldnt send heartbeat, websocket seems broken"); + break; + } + + last_heartbeat_timestamp = time::Instant::now(); + last_heartbeat_acknowledged = false; + } + } + } +} #[async_trait] // TODO: Make me not a trait!! pub trait HeartbeatHandlerCapable> { From 19f8403bcf30742061f4bd22f60cf7a3de615ed8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 17:07:08 +0100 Subject: [PATCH 064/130] Make HBHandler struct instead of trait Make HeartbeatHandler a generic struct instead of a struct with a trait. Reduces redundant code --- src/gateway/default/gateway.rs | 7 +++-- src/gateway/default/heartbeat.rs | 50 -------------------------------- src/gateway/default/mod.rs | 15 ++++------ src/gateway/mod.rs | 50 ++++++++++++++++++++------------ src/gateway/wasm/gateway.rs | 4 +-- src/gateway/wasm/heartbeat.rs | 40 ------------------------- src/gateway/wasm/mod.rs | 2 -- 7 files changed, 43 insertions(+), 125 deletions(-) delete mode 100644 src/gateway/default/heartbeat.rs delete mode 100644 src/gateway/wasm/heartbeat.rs diff --git a/src/gateway/default/gateway.rs b/src/gateway/default/gateway.rs index a8bb69c..73178ba 100644 --- a/src/gateway/default/gateway.rs +++ b/src/gateway/default/gateway.rs @@ -8,7 +8,7 @@ use crate::types::{self, WebSocketEvent}; #[derive(Debug)] pub struct DefaultGateway { events: Arc>, - heartbeat_handler: HeartbeatHandler, + heartbeat_handler: HeartbeatHandler>>, websocket_send: Arc< Mutex< SplitSink< @@ -30,7 +30,9 @@ impl WebSocketStream>, > for DefaultGateway { - fn get_heartbeat_handler(&self) -> &HeartbeatHandler { + fn get_heartbeat_handler( + &self, + ) -> &HeartbeatHandler>> { &self.heartbeat_handler } @@ -171,7 +173,6 @@ impl DefaultGateway { /// Deserializes and updates a dispatched event, when we already know its type; /// (Called for every event in handle_message) - #[allow(dead_code)] // TODO: Remove this allow annotation async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( data: &'a str, event: &mut GatewayEvent, diff --git a/src/gateway/default/heartbeat.rs b/src/gateway/default/heartbeat.rs deleted file mode 100644 index 4b9fff7..0000000 --- a/src/gateway/default/heartbeat.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::*; - -// TODO: Make me not a trait and delete this file -#[async_trait] -impl - HeartbeatHandlerCapable< - tokio_tungstenite::tungstenite::Message, - WebSocketStream>, - > for HeartbeatHandler -{ - fn get_send(&self) -> &Sender { - &self.send - } - - fn get_heartbeat_interval(&self) -> Duration { - self.heartbeat_interval - } - - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { - let (send, receive) = tokio::sync::mpsc::channel(32); - let kill_receive = kill_rc.resubscribe(); - - let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; - }); - - Self { - heartbeat_interval, - send, - handle, - } - } -} diff --git a/src/gateway/default/mod.rs b/src/gateway/default/mod.rs index ae9db05..d83b64e 100644 --- a/src/gateway/default/mod.rs +++ b/src/gateway/default/mod.rs @@ -1,6 +1,5 @@ pub mod gateway; pub mod handle; -pub mod heartbeat; use super::*; pub use gateway::*; @@ -15,16 +14,12 @@ use std::fmt::Debug; use std::sync::{Arc, RwLock}; use std::time::Duration; -use futures_util::stream::SplitSink; -use futures_util::stream::SplitStream; +use futures_util::stream::{SplitSink, SplitStream}; use log::{info, warn}; -use tokio::net::TcpStream; -use tokio::sync::mpsc::Sender; -use tokio::sync::Mutex; -use tokio::task; -use tokio::task::JoinHandle; -use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; +use tokio::{net::TcpStream, sync::Mutex, task}; +use tokio_tungstenite::{ + connect_async_tls_with_config, Connector, MaybeTlsStream, WebSocketStream, +}; impl crate::gateway::MessageCapable for tokio_tungstenite::tungstenite::Message { fn as_string(&self) -> Option { diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 44c8ee2..3318d6b 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -10,7 +10,7 @@ pub mod wasm; pub use default::*; pub use message::*; use safina_timer::sleep_until; -use tokio::task::JoinHandle; +use tokio::task::{self, JoinHandle}; // TODO: Uncomment for Prod! // #[cfg(all(target_arch = "wasm32", feature = "client"))] pub use wasm::*; @@ -25,6 +25,7 @@ use crate::types::{ use std::any::Any; use std::collections::HashMap; +use std::marker::PhantomData; use std::sync::{Arc, RwLock}; use std::time::{self, Duration, Instant}; @@ -169,7 +170,7 @@ where fn get_websocket_send(&self) -> Arc>>; fn get_store(&self) -> GatewayStore; fn get_url(&self) -> String; - fn get_heartbeat_handler(&self) -> &HeartbeatHandler; + fn get_heartbeat_handler(&self) -> &HeartbeatHandler; /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] /// /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation @@ -379,7 +380,7 @@ where op_code: Some(GATEWAY_HEARTBEAT), }; - let heartbeat_thread_communicator = self.get_heartbeat_handler().get_send(); + let heartbeat_thread_communicator = self.get_heartbeat_handler().send; heartbeat_thread_communicator .send(heartbeat_communication) @@ -408,7 +409,7 @@ where }; let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.get_send(); + let heartbeat_thread_communicator = heartbeat_handler.send; heartbeat_thread_communicator .send(heartbeat_communication) @@ -441,7 +442,7 @@ where }; let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.get_send(); + let heartbeat_thread_communicator = heartbeat_handler.send; heartbeat_thread_communicator .send(heartbeat_communication) .await @@ -559,19 +560,19 @@ where } /// Handles sending heartbeats to the gateway in another thread -#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used #[derive(Debug)] -pub struct HeartbeatHandler { +pub struct HeartbeatHandler> { /// How ofter heartbeats need to be sent at a minimum pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, /// The handle of the thread handle: JoinHandle<()>, + hb_type: (PhantomData, PhantomData), } -impl HeartbeatHandler { - pub async fn heartbeat_task + Send>( +impl + Send> HeartbeatHandler { + pub async fn heartbeat_task( websocket_tx: Arc>>, heartbeat_interval: Duration, mut receive: tokio::sync::mpsc::Receiver, @@ -651,17 +652,30 @@ impl HeartbeatHandler { } } } -} -#[async_trait] -// TODO: Make me not a trait!! -pub trait HeartbeatHandlerCapable> { - fn get_send(&self) -> &Sender; - fn get_heartbeat_interval(&self) -> Duration; - #[allow(clippy::new_ret_no_self)] - // TODO: new() has duplicated code in wasm and default impl. Can be fixed, if this is not a trait + fn new( heartbeat_interval: Duration, websocket_tx: Arc>>, kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler; + ) -> HeartbeatHandler { + let (send, receive) = tokio::sync::mpsc::channel(32); + let kill_receive = kill_rc.resubscribe(); + + let handle: JoinHandle<()> = task::spawn(async move { + HeartbeatHandler::heartbeat_task( + websocket_tx, + heartbeat_interval, + receive, + kill_receive, + ) + .await; + }); + + Self { + heartbeat_interval, + send, + handle, + hb_type: (PhantomData::, PhantomData::), + } + } } diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index cdbb991..b7ca51b 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -12,7 +12,7 @@ use crate::types::{self, GatewayReceivePayload}; #[derive(Debug)] pub struct WasmGateway { events: Arc>, - heartbeat_handler: HeartbeatHandler, + heartbeat_handler: HeartbeatHandler, websocket_send: Arc>>, websocket_receive: SplitStream, kill_send: tokio::sync::broadcast::Sender<()>, @@ -110,7 +110,7 @@ impl GatewayCapable for WasmGateway { )) } - fn get_heartbeat_handler(&self) -> &HeartbeatHandler { + fn get_heartbeat_handler(&self) -> &HeartbeatHandler { &self.heartbeat_handler } diff --git a/src/gateway/wasm/heartbeat.rs b/src/gateway/wasm/heartbeat.rs deleted file mode 100644 index 80f863e..0000000 --- a/src/gateway/wasm/heartbeat.rs +++ /dev/null @@ -1,40 +0,0 @@ -use tokio::task::{self, JoinHandle}; -use ws_stream_wasm::*; - -use super::*; - -#[async_trait] -impl HeartbeatHandlerCapable for HeartbeatHandler { - fn get_send(&self) -> &Sender { - &self.send - } - - fn get_heartbeat_interval(&self) -> Duration { - self.heartbeat_interval - } - - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc>>, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { - let (send, receive) = tokio::sync::mpsc::channel(32); - let kill_receive = kill_rc.resubscribe(); - - let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; - }); - - Self { - heartbeat_interval, - send, - handle, - } - } -} diff --git a/src/gateway/wasm/mod.rs b/src/gateway/wasm/mod.rs index 6cb87f2..e664ad2 100644 --- a/src/gateway/wasm/mod.rs +++ b/src/gateway/wasm/mod.rs @@ -1,10 +1,8 @@ pub mod gateway; pub mod handle; -pub mod heartbeat; use super::*; pub use gateway::*; pub use handle::*; -pub use heartbeat::*; use ws_stream_wasm::WsMessage; impl crate::gateway::MessageCapable for WsMessage { From 4f207d55d9df7c0353d35dd533863458b5196d51 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:08:53 +0100 Subject: [PATCH 065/130] prepare for platform-dependant websockets backend --- src/gateway/backend_tungstenite.rs | 69 ++++++++++++++++++++++++ src/gateway/gateway.rs | 86 ++++++++---------------------- src/gateway/handle.rs | 14 ++--- src/gateway/heartbeat.rs | 35 +++--------- src/gateway/message.rs | 36 ++----------- src/gateway/mod.rs | 12 ++--- 6 files changed, 110 insertions(+), 142 deletions(-) create mode 100644 src/gateway/backend_tungstenite.rs diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backend_tungstenite.rs new file mode 100644 index 0000000..0522847 --- /dev/null +++ b/src/gateway/backend_tungstenite.rs @@ -0,0 +1,69 @@ +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, +}; + +use super::GatewayMessage; +use crate::errors::GatewayError; + +#[derive(Debug, Clone)] +pub struct WebSocketBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WsSink = SplitSink>, tungstenite::Message>; +pub type WsStream = SplitStream>>; +pub type WsMessage = tungstenite::Message; +pub type WsError = tungstenite::Error; + +impl WebSocketBackend { + // When impl_trait_in_assoc_type gets stabilized, this would just be = impl Sink + + pub async fn new( + websocket_url: &str, + ) -> Result<(WsSink, WsStream), crate::errors::GatewayError> { + let mut roots = rustls::RootCertStore::empty(); + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") + { + roots.add(&rustls::Certificate(cert.0)).unwrap(); + } + let (websocket_stream, _) = match connect_async_tls_with_config( + websocket_url, + None, + false, + Some(Connector::Rustls( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() + .into(), + )), + ) + .await + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(GatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + Ok(websocket_stream.split()) + } +} + +impl From for tungstenite::Message { + fn from(message: GatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for GatewayMessage { + fn from(value: tungstenite::Message) -> Self { + Self(value.to_string()) + } +} diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 30d0610..b2ae673 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,4 +1,6 @@ use self::event::Events; +use super::handle::GatewayHandle; +use super::heartbeat::HeartbeatHandler; use super::*; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, @@ -10,15 +12,8 @@ use crate::types::{ pub struct Gateway { events: Arc>, heartbeat_handler: HeartbeatHandler, - websocket_send: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, - websocket_receive: SplitStream>>, + websocket_send: Arc>, + websocket_receive: WsStream, kill_send: tokio::sync::broadcast::Sender<()>, store: Arc>>>>, url: String, @@ -27,34 +22,7 @@ pub struct Gateway { impl Gateway { #[allow(clippy::new_ret_no_self)] pub async fn new(websocket_url: String) -> Result { - let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") - { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - } - let (websocket_stream, _) = match connect_async_tls_with_config( - &websocket_url, - None, - false, - Some(Connector::Rustls( - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots) - .with_no_client_auth() - .into(), - )), - ) - .await - { - Ok(websocket_stream) => websocket_stream, - Err(e) => { - return Err(GatewayError::CannotConnect { - error: e.to_string(), - }) - } - }; - - let (websocket_send, mut websocket_receive) = websocket_stream.split(); + let (websocket_send, mut websocket_receive) = WebSocketBackend::new(&websocket_url).await?; let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); @@ -63,9 +31,8 @@ impl Gateway { // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread - let msg = websocket_receive.next().await.unwrap().unwrap(); - let gateway_payload: types::GatewayReceivePayload = - serde_json::from_str(msg.to_text().unwrap()).unwrap(); + let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { return Err(GatewayError::NonHelloOnInitiate { @@ -120,8 +87,7 @@ impl Gateway { // This if chain can be much better but if let is unstable on stable rust if let Some(Ok(message)) = msg { - self.handle_message(GatewayMessage::from_tungstenite_message(message)) - .await; + self.handle_message(message.into()).await; continue; } @@ -134,7 +100,7 @@ impl Gateway { /// Closes the websocket connection and stops all tasks async fn close(&mut self) { self.kill_send.send(()).unwrap(); - self.websocket_send.lock().await.close().await.unwrap(); + let _ = self.websocket_send.lock().await.close().await; } /// Deserializes and updates a dispatched event, when we already know its type; @@ -156,31 +122,23 @@ impl Gateway { /// This handles a message as a websocket event and updates its events along with the events' observers pub async fn handle_message(&mut self, msg: GatewayMessage) { - if msg.is_empty() { + if msg.0.is_empty() { return; } - if !msg.is_error() && !msg.is_payload() { - warn!( - "Message unrecognised: {:?}, please open an issue on the chorus github", - msg.message.to_string() - ); + let Ok(gateway_payload) = msg.payload() else { + if let Some(error) = msg.error() { + warn!("GW: Received error {:?}, connection will close..", error); + self.close().await; + self.events.lock().await.error.notify(error).await; + } else { + warn!( + "Message unrecognised: {:?}, please open an issue on the chorus github", + msg.0 + ); + } return; - } - - if msg.is_error() { - let error = msg.error().unwrap(); - - warn!("GW: Received error {:?}, connection will close..", error); - - self.close().await; - - self.events.lock().await.error.notify(error).await; - - return; - } - - let gateway_payload = msg.payload().unwrap(); + }; // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes match gateway_payload.op_code { diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 1200b30..c38d987 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -9,14 +9,7 @@ use crate::types::{self, Composite}; pub struct GatewayHandle { pub url: String, pub events: Arc>, - pub websocket_send: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, + pub websocket_send: Arc>, /// Tells gateway tasks to close pub(super) kill_send: tokio::sync::broadcast::Sender<()>, pub(crate) store: Arc>>>>, @@ -32,13 +25,12 @@ impl GatewayHandle { }; let payload_json = serde_json::to_string(&gateway_payload).unwrap(); - - let message = tokio_tungstenite::tungstenite::Message::text(payload_json); + let message = GatewayMessage(payload_json); self.websocket_send .lock() .await - .send(message) + .send(message.into()) .await .unwrap(); } diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index dd162b7..aeb3dae 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -1,6 +1,5 @@ -use crate::types; - use super::*; +use crate::types; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; @@ -20,27 +19,14 @@ pub(super) struct HeartbeatHandler { impl HeartbeatHandler { pub fn new( heartbeat_interval: Duration, - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, + websocket_tx: Arc>, kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { + ) -> Self { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); Self { @@ -55,14 +41,7 @@ impl HeartbeatHandler { /// Can be killed by the kill broadcast; /// If the websocket is closed, will die out next time it tries to send a heartbeat; pub async fn heartbeat_task( - websocket_tx: Arc< - Mutex< - SplitSink< - WebSocketStream>, - tokio_tungstenite::tungstenite::Message, - >, - >, - >, + websocket_tx: Arc>, heartbeat_interval: Duration, mut receive: tokio::sync::mpsc::Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, @@ -122,9 +101,9 @@ impl HeartbeatHandler { let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); + let msg = GatewayMessage(heartbeat_json); - let send_result = websocket_tx.lock().await.send(msg).await; + let send_result = websocket_tx.lock().await.send(msg.into()).await; if send_result.is_err() { // We couldn't send, the websocket is broken warn!("GW: Couldnt send heartbeat, websocket seems broken"); diff --git a/src/gateway/message.rs b/src/gateway/message.rs index edee9dd..8a94f73 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -5,24 +5,14 @@ use super::*; /// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] -pub struct GatewayMessage { - /// The message we received from the server - pub(super) message: tokio_tungstenite::tungstenite::Message, -} +pub struct GatewayMessage(pub String); impl GatewayMessage { - /// Creates self from a tungstenite message - pub fn from_tungstenite_message(message: tokio_tungstenite::tungstenite::Message) -> Self { - Self { message } - } - /// Parses the message as an error; /// Returns the error if succesfully parsed, None if the message isn't an error pub fn error(&self) -> Option { - let content = self.message.to_string(); - // Some error strings have dots on the end, which we don't care about - let processed_content = content.to_lowercase().replace('.', ""); + let processed_content = self.0.to_lowercase().replace('.', ""); match processed_content.as_str() { "unknown error" | "4000" => Some(GatewayError::Unknown), @@ -45,29 +35,9 @@ impl GatewayMessage { } } - /// Returns whether or not the message is an error - pub fn is_error(&self) -> bool { - self.error().is_some() - } - /// Parses the message as a payload; /// Returns a result of deserializing pub fn payload(&self) -> Result { - return serde_json::from_str(self.message.to_text().unwrap()); - } - - /// Returns whether or not the message is a payload - pub fn is_payload(&self) -> bool { - // close messages are never payloads, payloads are only text messages - if self.message.is_close() | !self.message.is_text() { - return false; - } - - return self.payload().is_ok(); - } - - /// Returns whether or not the message is empty - pub fn is_empty(&self) -> bool { - self.message.is_empty() + return serde_json::from_str(&self.0); } } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index ebd06cc..6e9d1c7 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -3,8 +3,13 @@ pub mod handle; pub mod heartbeat; pub mod message; +#[cfg(not(wasm))] +pub mod backend_tungstenite; +#[cfg(not(wasm))] +use backend_tungstenite::*; + pub use gateway::*; -pub use handle::*; +pub use handle::GatewayHandle; use heartbeat::*; pub use message::*; @@ -19,20 +24,15 @@ use std::sync::{Arc, RwLock}; use std::time::Duration; use tokio::time::sleep_until; -use futures_util::stream::SplitSink; -use futures_util::stream::SplitStream; use futures_util::SinkExt; use futures_util::StreamExt; use log::{info, trace, warn}; -use tokio::net::TcpStream; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; use tokio::task; use tokio::task::JoinHandle; use tokio::time; use tokio::time::Instant; -use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] From 0f446f43b417036ba24419dcc114d7049e6b5f7b Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:13:52 +0100 Subject: [PATCH 066/130] removed outdated comment --- src/gateway/backend_tungstenite.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backend_tungstenite.rs index 0522847..e114e2c 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backend_tungstenite.rs @@ -20,8 +20,6 @@ pub type WsMessage = tungstenite::Message; pub type WsError = tungstenite::Error; impl WebSocketBackend { - // When impl_trait_in_assoc_type gets stabilized, this would just be = impl Sink - pub async fn new( websocket_url: &str, ) -> Result<(WsSink, WsStream), crate::errors::GatewayError> { From dd9945068f2ff1be12e23f6d27077617bc69c13a Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:15:00 +0100 Subject: [PATCH 067/130] removed leftover type aliases --- src/gateway/backend_tungstenite.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backend_tungstenite.rs index e114e2c..a81bab3 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backend_tungstenite.rs @@ -16,8 +16,6 @@ pub struct WebSocketBackend; // These could be made into inherent associated types when that's stabilized pub type WsSink = SplitSink>, tungstenite::Message>; pub type WsStream = SplitStream>>; -pub type WsMessage = tungstenite::Message; -pub type WsError = tungstenite::Error; impl WebSocketBackend { pub async fn new( From c0ce540da68422f65286b8680723ce748c7ddaed Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:18:08 +0100 Subject: [PATCH 068/130] for got unwrap :3 --- src/gateway/gateway.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index b2ae673..f0d1882 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -100,7 +100,7 @@ impl Gateway { /// Closes the websocket connection and stops all tasks async fn close(&mut self) { self.kill_send.send(()).unwrap(); - let _ = self.websocket_send.lock().await.close().await; + self.websocket_send.lock().await.close().await.unwrap(); } /// Deserializes and updates a dispatched event, when we already know its type; From 5bd8f32a6abf3f0fc29ee27e69a3ca866b064b0c Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:46:49 +0100 Subject: [PATCH 069/130] remove superfluous return --- src/gateway/backend_tungstenite.rs | 2 +- src/gateway/gateway.rs | 3 ++- src/gateway/message.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backend_tungstenite.rs index a81bab3..53b6982 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backend_tungstenite.rs @@ -18,7 +18,7 @@ pub type WsSink = SplitSink>, tungsten pub type WsStream = SplitStream>>; impl WebSocketBackend { - pub async fn new( + pub async fn connect( websocket_url: &str, ) -> Result<(WsSink, WsStream), crate::errors::GatewayError> { let mut roots = rustls::RootCertStore::empty(); diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index f0d1882..38efe7c 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -22,7 +22,8 @@ pub struct Gateway { impl Gateway { #[allow(clippy::new_ret_no_self)] pub async fn new(websocket_url: String) -> Result { - let (websocket_send, mut websocket_receive) = WebSocketBackend::new(&websocket_url).await?; + let (websocket_send, mut websocket_receive) = + WebSocketBackend::connect(&websocket_url).await?; let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 8a94f73..2c12e48 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -38,6 +38,6 @@ impl GatewayMessage { /// Parses the message as a payload; /// Returns a result of deserializing pub fn payload(&self) -> Result { - return serde_json::from_str(&self.0); + serde_json::from_str(&self.0) } } From 0e16e55d648a158356e650545f5578d17c157448 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 18:27:49 +0100 Subject: [PATCH 070/130] Fix references to heartbeat_thread_communicator --- src/gateway/mod.rs | 8 ++++---- src/gateway/wasm/gateway.rs | 2 +- src/gateway/wasm/handle.rs | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 3318d6b..c2965c3 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -380,7 +380,7 @@ where op_code: Some(GATEWAY_HEARTBEAT), }; - let heartbeat_thread_communicator = self.get_heartbeat_handler().send; + let heartbeat_thread_communicator = &self.get_heartbeat_handler().send; heartbeat_thread_communicator .send(heartbeat_communication) @@ -409,7 +409,7 @@ where }; let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.send; + let heartbeat_thread_communicator = &heartbeat_handler.send; heartbeat_thread_communicator .send(heartbeat_communication) @@ -442,7 +442,7 @@ where }; let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = heartbeat_handler.send; + let heartbeat_thread_communicator = &heartbeat_handler.send; heartbeat_thread_communicator .send(heartbeat_communication) .await @@ -571,7 +571,7 @@ pub struct HeartbeatHandler> { hb_type: (PhantomData, PhantomData), } -impl + Send> HeartbeatHandler { +impl + Send + 'static> HeartbeatHandler { pub async fn heartbeat_task( websocket_tx: Arc>>, heartbeat_interval: Duration, diff --git a/src/gateway/wasm/gateway.rs b/src/gateway/wasm/gateway.rs index b7ca51b..516ac9e 100644 --- a/src/gateway/wasm/gateway.rs +++ b/src/gateway/wasm/gateway.rs @@ -41,7 +41,7 @@ impl GatewayCapable for WasmGateway { async fn spawn>( websocket_url: String, ) -> Result { - let (_, mut websocket_stream) = match WsMeta::connect(websocket_url.clone(), None).await { + let (_, websocket_stream) = match WsMeta::connect(websocket_url.clone(), None).await { Ok(ws) => Ok(ws), Err(e) => Err(GatewayError::CannotConnect { error: e.to_string(), diff --git a/src/gateway/wasm/handle.rs b/src/gateway/wasm/handle.rs index c9ad14d..3e77ed3 100644 --- a/src/gateway/wasm/handle.rs +++ b/src/gateway/wasm/handle.rs @@ -14,7 +14,7 @@ pub struct WasmGatewayHandle { pub(crate) store: GatewayStore, } -#[async_trait] +#[async_trait(?Send)] impl GatewayHandleCapable for WasmGatewayHandle { fn new( url: String, @@ -31,4 +31,20 @@ impl GatewayHandleCapable for WasmGatewayHandle { store, } } + + async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { + self.send_json_event(op_code, to_send).await + } + + async fn observe + Send + Sync>( + &self, + object: Arc>, + ) -> Arc> { + self.observe(object).await + } + + async fn close(&self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } } From a4d5ebb6895212f327101e0d0f07d9c91d082b00 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 19:12:29 +0100 Subject: [PATCH 071/130] Resolve merge conflicts --- examples/gateway_observers.rs | 6 +- examples/gateway_simple.rs | 7 +- src/api/auth/login.rs | 5 +- src/api/auth/mod.rs | 6 +- src/api/auth/register.rs | 3 +- src/gateway/events.rs | 1 + src/gateway/gateway.rs | 11 +- src/gateway/handle.rs | 168 +++++++++ src/gateway/heartbeat.rs | 12 +- src/gateway/mod.rs | 575 +----------------------------- src/instance.rs | 4 +- src/lib.rs | 17 - src/types/entities/channel.rs | 5 +- src/types/entities/emoji.rs | 5 +- src/types/entities/guild.rs | 5 +- src/types/entities/mod.rs | 5 +- src/types/entities/role.rs | 5 +- src/types/entities/user.rs | 5 +- src/types/entities/voice_state.rs | 5 +- src/types/entities/webhook.rs | 5 +- tests/common/mod.rs | 4 +- tests/gateway.rs | 10 +- 22 files changed, 253 insertions(+), 616 deletions(-) create mode 100644 src/gateway/handle.rs diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 7d49a1f..d4e690c 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,11 +1,9 @@ use async_trait::async_trait; -use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; -use chorus::GatewayHandle; +use chorus::gateway::Gateway; use chorus::{ self, gateway::Observer, types::{GatewayIdentifyPayload, GatewayReady}, - Gateway, }; use std::{sync::Arc, time::Duration}; use tokio::{self, time::sleep}; @@ -33,7 +31,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection - let gateway: GatewayHandle = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 56c557a..a9c019b 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,8 +1,7 @@ use std::time::Duration; -use chorus::gateway::{GatewayCapable, GatewayHandleCapable}; -use chorus::GatewayHandle; -use chorus::{self, types::GatewayIdentifyPayload, Gateway}; +use chorus::gateway::Gateway; +use chorus::{self, types::GatewayIdentifyPayload}; use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event @@ -12,7 +11,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway: GatewayHandle = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 5eddfa3..1d9fc8a 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -4,11 +4,10 @@ use reqwest::Client; use serde_json::to_string; use crate::errors::ChorusResult; -use crate::gateway::{DefaultGatewayHandle, GatewayCapable, GatewayHandleCapable}; +use crate::gateway::Gateway; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; -use crate::Gateway; impl Instance { /// Logs into an existing account on the spacebar server. @@ -37,7 +36,7 @@ impl Instance { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } let mut identify = GatewayIdentifyPayload::common(); - let gateway: DefaultGatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); + let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = login_result.token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index a1490fc..ae3b219 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -3,10 +3,9 @@ use std::sync::{Arc, RwLock}; pub use login::*; pub use register::*; -use crate::gateway::{DefaultGatewayHandle, GatewayCapable, GatewayHandleCapable}; +use crate::gateway::Gateway; use crate::{ errors::ChorusResult, - gateway::DefaultGateway, instance::{ChorusUser, Instance}, types::{GatewayIdentifyPayload, User}, }; @@ -26,8 +25,7 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway: DefaultGatewayHandle = - DefaultGateway::spawn(self.urls.wss.clone()).await.unwrap(); + let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); gateway.send_identify(identify).await; let user = ChorusUser::new( diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 2ea6e7d..2ea7d57 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock}; use reqwest::Client; use serde_json::to_string; -use crate::gateway::{GatewayCapable, GatewayHandleCapable}; +use crate::gateway::{Gateway, GatewayHandle}; use crate::types::GatewayIdentifyPayload; use crate::{ errors::ChorusResult, @@ -12,7 +12,6 @@ use crate::{ types::LimitType, types::RegisterSchema, }; -use crate::{Gateway, GatewayHandle}; impl Instance { /// Registers a new user on the server. diff --git a/src/gateway/events.rs b/src/gateway/events.rs index bc8565e..fdb7b25 100644 --- a/src/gateway/events.rs +++ b/src/gateway/events.rs @@ -1,4 +1,5 @@ use super::*; +use crate::types; #[derive(Default, Debug)] pub struct Events { diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 38efe7c..9e3410c 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,7 +1,12 @@ +use std::time::Duration; + +use futures_util::{SinkExt, StreamExt}; +use log::*; +use tokio::task; + use self::event::Events; -use super::handle::GatewayHandle; -use super::heartbeat::HeartbeatHandler; use super::*; +use super::{WsSink, WsStream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, @@ -21,7 +26,7 @@ pub struct Gateway { impl Gateway { #[allow(clippy::new_ret_no_self)] - pub async fn new(websocket_url: String) -> Result { + pub async fn spawn(websocket_url: String) -> Result { let (websocket_send, mut websocket_receive) = WebSocketBackend::connect(&websocket_url).await?; diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs new file mode 100644 index 0000000..9a3c509 --- /dev/null +++ b/src/gateway/handle.rs @@ -0,0 +1,168 @@ +use futures_util::SinkExt; +use log::*; + +use std::fmt::Debug; + +use super::{event::Events, *}; +use crate::types::{self, Composite}; + +/// Represents a handle to a Gateway connection. A Gateway connection will create observable +/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently +/// implemented types with the trait [`WebSocketEvent`] +/// Using this handle you can also send Gateway Events directly. +#[derive(Debug, Clone)] +pub struct GatewayHandle { + pub url: String, + pub events: Arc>, + pub websocket_send: Arc>, + /// Tells gateway tasks to close + pub(super) kill_send: tokio::sync::broadcast::Sender<()>, + pub(crate) store: Arc>>>>, +} + +impl GatewayHandle { + /// Sends json to the gateway with an opcode + async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value) { + let gateway_payload = types::GatewaySendPayload { + op_code, + event_data: Some(to_send), + sequence_number: None, + }; + + let payload_json = serde_json::to_string(&gateway_payload).unwrap(); + let message = GatewayMessage(payload_json); + + self.websocket_send + .lock() + .await + .send(message.into()) + .await + .unwrap(); + } + + pub async fn observe>( + &self, + object: Arc>, + ) -> Arc> { + let mut store = self.store.lock().await; + let id = object.read().unwrap().id(); + if let Some(channel) = store.get(&id) { + let object = channel.clone(); + drop(store); + object + .read() + .unwrap() + .downcast_ref::() + .unwrap_or_else(|| { + panic!( + "Snowflake {} already exists in the store, but it is not of type T.", + id + ) + }); + let ptr = Arc::into_raw(object.clone()); + // SAFETY: + // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. + // - This operation doesn't read or write any shared data, and thus cannot cause a data race + // - The reference count is not being modified + let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock).clone() }; + let object = downcasted.read().unwrap().clone(); + + let watched_object = object.watch_whole(self).await; + *downcasted.write().unwrap() = watched_object; + downcasted + } else { + let id = object.read().unwrap().id(); + let object = object.read().unwrap().clone(); + let object = object.clone().watch_whole(self).await; + let wrapped = Arc::new(RwLock::new(object)); + store.insert(id, wrapped.clone()); + wrapped + } + } + + /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` + /// with all of its observable fields being observed. + pub async fn observe_and_into_inner>( + &self, + object: Arc>, + ) -> T { + let channel = self.observe(object.clone()).await; + let object = channel.read().unwrap().clone(); + object + } + + /// Sends an identify event to the gateway + pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Identify.."); + + self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; + } + + /// Sends a resume event to the gateway + pub async fn send_resume(&self, to_send: types::GatewayResume) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Resume.."); + + self.send_json_event(GATEWAY_RESUME, to_send_value).await; + } + + /// Sends an update presence event to the gateway + pub async fn send_update_presence(&self, to_send: types::UpdatePresence) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Update Presence.."); + + self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) + .await; + } + + /// Sends a request guild members to the server + pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Request Guild Members.."); + + self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) + .await; + } + + /// Sends an update voice state to the server + pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { + let to_send_value = serde_json::to_value(to_send).unwrap(); + + trace!("GW: Sending Update Voice State.."); + + self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) + .await; + } + + /// Sends a call sync to the server + pub async fn send_call_sync(&self, to_send: types::CallSync) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Call Sync.."); + + self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; + } + + /// Sends a Lazy Request + pub async fn send_lazy_request(&self, to_send: types::LazyRequest) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("GW: Sending Lazy Request.."); + + self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) + .await; + } + + /// Closes the websocket connection and stops all gateway tasks; + /// + /// Esentially pulls the plug on the gateway, leaving it possible to resume; + pub async fn close(&self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } +} diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index aeb3dae..a5875a4 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -1,3 +1,11 @@ +use futures_util::SinkExt; +use log::*; +use std::time::{self, Duration, Instant}; +use tokio::sync::mpsc::{Receiver, Sender}; + +use safina_timer::sleep_until; +use tokio::task::{self, JoinHandle}; + use super::*; use crate::types; @@ -43,13 +51,15 @@ impl HeartbeatHandler { pub async fn heartbeat_task( websocket_tx: Arc>, heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, + mut receive: Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, ) { let mut last_heartbeat_timestamp: Instant = time::Instant::now(); let mut last_heartbeat_acknowledged = true; let mut last_seq_number: Option = None; + safina_timer::start_timer_thread(); + loop { if kill_receive.try_recv().is_ok() { trace!("GW: Closing heartbeat task"); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 9ab4f9d..269a274 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,38 +1,32 @@ +use async_trait::async_trait; + +pub mod backend_tungstenite; pub mod events; +pub mod gateway; +pub mod handle; +pub mod heartbeat; pub mod message; -#[cfg(not(wasm))] -pub mod backend_tungstenite; -#[cfg(not(wasm))] -use backend_tungstenite::*; - +pub use gateway::*; +pub use handle::*; +use heartbeat::*; pub use message::*; -use safina_timer::sleep_until; -use tokio::task::{self, JoinHandle}; -use self::events::Events; use crate::errors::GatewayError; -use crate::types::{ - self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, - ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, - Snowflake, SourceUrlField, ThreadUpdate, UpdateMessage, WebSocketEvent, -}; +use crate::types::{Snowflake, WebSocketEvent}; use std::any::Any; use std::collections::HashMap; -use std::marker::PhantomData; use std::sync::{Arc, RwLock}; -use std::time::{self, Duration, Instant}; -use futures_util::SinkExt; -use log::{info, trace, warn}; -use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; -pub type GatewayStore = Arc>>>>; - -/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; +#[cfg(not(target_arch = "wasm32"))] +pub type WsSink = backend_tungstenite::WsSink; +#[cfg(not(target_arch = "wasm32"))] +pub type WsStream = backend_tungstenite::WsStream; +#[cfg(not(target_arch = "wasm32"))] +pub type WebSocketBackend = backend_tungstenite::WebSocketBackend; // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] @@ -82,25 +76,8 @@ const GATEWAY_CALL_SYNC: u8 = 13; /// See [types::LazyRequest] const GATEWAY_LAZY_REQUEST: u8 = 14; -pub trait MessageCapable { - fn as_string(&self) -> Option; - fn as_bytes(&self) -> Option>; - fn is_empty(&self) -> bool; - fn from_str(s: &str) -> Self; -} - pub type ObservableObject = dyn Send + Sync + Any; -/// Used for communications between the heartbeat and gateway thread. -/// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server -#[derive(Clone, Copy, Debug)] -pub struct HeartbeatThreadCommunication { - /// The opcode for the communication we received, if relevant - pub op_code: Option, - /// The sequence number we got from discord, if any - pub sequence_number: Option, -} - /// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. pub trait Updateable: 'static + Send + Sync { fn id(&self) -> Snowflake; @@ -151,523 +128,3 @@ impl GatewayEvent { } } } - -#[async_trait] -pub trait GatewayCapable -where - T: MessageCapable + Send + 'static, - S: Sink + Send, -{ - fn get_events(&self) -> Arc>; - fn get_websocket_send(&self) -> Arc>>; - fn get_store(&self) -> GatewayStore; - fn get_url(&self) -> String; - fn get_heartbeat_handler(&self) -> &HeartbeatHandler; - /// Returns a Result with a matching impl of [`GatewayHandleCapable`], or a [`GatewayError`] - /// - /// DOCUMENTME: Explain what this method has to do to be a good get_handle() impl, or link to such documentation - /// TODO: Give spawn a default trait impl, avoid code duplication - async fn spawn>(websocket_url: String) - -> Result; - async fn close(&mut self); - /// This handles a message as a websocket event and updates its events along with the events' observers - async fn handle_message(&mut self, msg: GatewayMessage) { - if msg.is_empty() { - return; - } - - if !msg.is_error() && !msg.is_payload() { - warn!( - "Message unrecognised: {:?}, please open an issue on the chorus github", - msg.message.to_string() - ); - return; - } - - if msg.is_error() { - let error = msg.error().unwrap(); - - warn!("GW: Received error {:?}, connection will close..", error); - - self.close().await; - - let events = self.get_events(); - let events = events.lock().await; - - events.error.notify(error).await; - - return; - } - - let gateway_payload = msg.payload().unwrap(); - println!("gateway payload: {:#?}", &gateway_payload); - - // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes - match gateway_payload.op_code { - // An event was dispatched, we need to look at the gateway event name t - GATEWAY_DISPATCH => { - let Some(event_name) = gateway_payload.event_name else { - warn!("Gateway dispatch op without event_name"); - return; - }; - - trace!("Gateway: Received {event_name}"); - - macro_rules! handle { - ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { - match event_name.as_str() { - $($name => { - let events = self.get_events(); - let event = &mut events.lock().await.$($path).+; - let json = gateway_payload.event_data.unwrap().get(); - match serde_json::from_str(json) { - Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), - Ok(message) => { - $( - let mut message: $message_type = message; - let store = self.get_store(); - let store = store.lock().await; - let id = if message.id().is_some() { - message.id().unwrap() - } else { - event.notify(message).await; - return; - }; - if let Some(to_update) = store.get(&id) { - let object = to_update.clone(); - let inner_object = object.read().unwrap(); - if let Some(_) = inner_object.downcast_ref::<$update_type>() { - let ptr = Arc::into_raw(object.clone()); - // SAFETY: - // - We have just checked that the typeid of the `dyn Any ...` matches that of `T`. - // - This operation doesn't read or write any shared data, and thus cannot cause a data race - // - The reference count is not being modified - let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() }; - drop(inner_object); - message.set_json(json.to_string()); - message.set_source_url(self.get_url().clone()); - message.update(downcasted.clone()); - } else { - warn!("Received {} for {}, but it has been observed to be a different type!", $name, id) - } - } - )? - event.notify(message).await; - } - } - },)* - "RESUMED" => (), - "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); - match result { - Err(err) => { - warn!( - "Failed to parse gateway event {} ({})", - event_name, - err - ); - return; - } - Ok(sessions) => { - let events = self.get_events(); - let events = events.lock().await; - events.session.replace.notify( - types::SessionsReplace {sessions} - ).await; - } - } - }, - _ => { - warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); - } - } - }; - } - - // See https://discord.com/developers/docs/topics/gateway-events#receive-events - // "Some" of these are undocumented - handle!( - "READY" => session.ready, - "READY_SUPPLEMENTAL" => session.ready_supplemental, - "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, - "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, - "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule, - "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, - "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, - "CHANNEL_CREATE" => channel.create ChannelCreate: Guild, - "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, - "CHANNEL_UNREAD_UPDATE" => channel.unread_update, - "CHANNEL_DELETE" => channel.delete ChannelDelete: Guild, - "CHANNEL_PINS_UPDATE" => channel.pins_update, - "CALL_CREATE" => call.create, - "CALL_UPDATE" => call.update, - "CALL_DELETE" => call.delete, - "THREAD_CREATE" => thread.create, // TODO - "THREAD_UPDATE" => thread.update ThreadUpdate: Channel, - "THREAD_DELETE" => thread.delete, // TODO - "THREAD_LIST_SYNC" => thread.list_sync, // TODO - "THREAD_MEMBER_UPDATE" => thread.member_update, // TODO - "THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO - "GUILD_CREATE" => guild.create, // TODO - "GUILD_UPDATE" => guild.update, // TODO - "GUILD_DELETE" => guild.delete, // TODO - "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, - "GUILD_BAN_ADD" => guild.ban_add, // TODO - "GUILD_BAN_REMOVE" => guild.ban_remove, // TODO - "GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO - "GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO - "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, - "GUILD_MEMBER_ADD" => guild.member_add, - "GUILD_MEMBER_REMOVE" => guild.member_remove, - "GUILD_MEMBER_UPDATE" => guild.member_update, // TODO - "GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO - "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild, - "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject, - "GUILD_ROLE_DELETE" => guild.role_delete, // TODO - "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO - "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO - "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO - "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, - "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, - "PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO - "INTEGRATION_CREATE" => integration.create, // TODO - "INTEGRATION_UPDATE" => integration.update, // TODO - "INTEGRATION_DELETE" => integration.delete, // TODO - "INTERACTION_CREATE" => interaction.create, // TODO - "INVITE_CREATE" => invite.create, // TODO - "INVITE_DELETE" => invite.delete, // TODO - "MESSAGE_CREATE" => message.create, - "MESSAGE_UPDATE" => message.update, // TODO - "MESSAGE_DELETE" => message.delete, - "MESSAGE_DELETE_BULK" => message.delete_bulk, - "MESSAGE_REACTION_ADD" => message.reaction_add, // TODO - "MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO - "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO - "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO - "MESSAGE_ACK" => message.ack, - "PRESENCE_UPDATE" => user.presence_update, // TODO - "RELATIONSHIP_ADD" => relationship.add, - "RELATIONSHIP_REMOVE" => relationship.remove, - "STAGE_INSTANCE_CREATE" => stage_instance.create, - "STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO - "STAGE_INSTANCE_DELETE" => stage_instance.delete, - "TYPING_START" => user.typing_start, - "USER_UPDATE" => user.update, // TODO - "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, - "VOICE_STATE_UPDATE" => voice.state_update, // TODO - "VOICE_SERVER_UPDATE" => voice.server_update, - "WEBHOOKS_UPDATE" => webhooks.update - ); - } - // We received a heartbeat from the server - // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." - GATEWAY_HEARTBEAT => { - trace!("GW: Received Heartbeat // Heartbeat Request"); - - // Tell the heartbeat handler it should send a heartbeat right away - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT), - }; - - let heartbeat_thread_communicator = &self.get_heartbeat_handler().send; - - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_RECONNECT => { - todo!() - } - GATEWAY_INVALID_SESSION => { - todo!() - } - // Starts our heartbeat - // We should have already handled this in gateway init - GATEWAY_HELLO => { - warn!("Received hello when it was unexpected"); - } - GATEWAY_HEARTBEAT_ACK => { - trace!("GW: Received Heartbeat ACK"); - - // Tell the heartbeat handler we received an ack - - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: gateway_payload.sequence_number, - op_code: Some(GATEWAY_HEARTBEAT_ACK), - }; - - let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = &heartbeat_handler.send; - - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - GATEWAY_IDENTIFY - | GATEWAY_UPDATE_PRESENCE - | GATEWAY_UPDATE_VOICE_STATE - | GATEWAY_RESUME - | GATEWAY_REQUEST_GUILD_MEMBERS - | GATEWAY_CALL_SYNC - | GATEWAY_LAZY_REQUEST => { - info!( - "Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", - gateway_payload.op_code - ); - } - _ => { - warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); - } - } - - // If we we received a seq number we should let it know - if let Some(seq_num) = gateway_payload.sequence_number { - let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: Some(seq_num), - // Op code is irrelevant here - op_code: None, - }; - - let heartbeat_handler = self.get_heartbeat_handler(); - let heartbeat_thread_communicator = &heartbeat_handler.send; - heartbeat_thread_communicator - .send(heartbeat_communication) - .await - .unwrap(); - } - } -} - -#[async_trait(?Send)] -pub trait GatewayHandleCapable -where - T: MessageCapable + Send + 'static, - S: Sink, -{ - fn new( - url: String, - events: Arc>, - websocket_send: Arc>>, - kill_send: tokio::sync::broadcast::Sender<()>, - store: GatewayStore, - ) -> Self; - - /// Sends json to the gateway with an opcode - async fn send_json_event(&self, op_code: u8, to_send: serde_json::Value); - - /// Observes an Item ``, which will update itself, if new information about this - /// item arrives on the corresponding Gateway Thread - async fn observe + Send + Sync>( - &self, - object: Arc>, - ) -> Arc>; - - /// Recursively observes and updates all updateable fields on the struct T. Returns an object `T` - /// with all of its observable fields being observed. - async fn observe_and_into_inner>( - &self, - object: Arc>, - ) -> U { - let channel = self.observe(object.clone()).await; - let object = channel.read().unwrap().clone(); - object - } - - /// Sends an identify event to the gateway - async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Identify.."); - - self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; - } - - /// Sends an update presence event to the gateway - async fn send_update_presence(&self, to_send: types::UpdatePresence) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Update Presence.."); - - self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) - .await; - } - - /// Sends a resume event to the gateway - async fn send_resume(&self, to_send: types::GatewayResume) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Resume.."); - - self.send_json_event(GATEWAY_RESUME, to_send_value).await; - } - - /// Sends a request guild members to the server - async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Request Guild Members.."); - - self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) - .await; - } - - /// Sends an update voice state to the server - async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { - let to_send_value = serde_json::to_value(to_send).unwrap(); - - trace!("GW: Sending Update Voice State.."); - - self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) - .await; - } - - /// Sends a call sync to the server - async fn send_call_sync(&self, to_send: types::CallSync) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Call Sync.."); - - self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; - } - - /// Sends a Lazy Request - async fn send_lazy_request(&self, to_send: types::LazyRequest) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); - - trace!("GW: Sending Lazy Request.."); - - self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) - .await; - } - - /// Closes the websocket connection and stops all gateway tasks; - /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; - async fn close(&self); -} - -/// Handles sending heartbeats to the gateway in another thread -#[derive(Debug)] -pub struct HeartbeatHandler> { - /// How ofter heartbeats need to be sent at a minimum - pub heartbeat_interval: Duration, - /// The send channel for the heartbeat thread - pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, - hb_type: (PhantomData, PhantomData), -} - -impl + Send + 'static> HeartbeatHandler { - pub async fn heartbeat_task( - websocket_tx: Arc>>, - heartbeat_interval: Duration, - mut receive: tokio::sync::mpsc::Receiver, - mut kill_receive: tokio::sync::broadcast::Receiver<()>, - ) { - let mut last_heartbeat_timestamp: Instant = time::Instant::now(); - let mut last_heartbeat_acknowledged = true; - let mut last_seq_number: Option = None; - safina_timer::start_timer_thread(); - - loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - - let timeout = if last_heartbeat_acknowledged { - heartbeat_interval - } else { - // If the server hasn't acknowledged our heartbeat we should resend it - Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) - }; - - let mut should_send = false; - - tokio::select! { - () = sleep_until(last_heartbeat_timestamp + timeout) => { - should_send = true; - } - Some(communication) = receive.recv() => { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; - } - - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; - } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} - } - } - } - } - - if should_send { - trace!("GW: Sending Heartbeat.."); - - let heartbeat = types::GatewayHeartbeat { - op: GATEWAY_HEARTBEAT, - d: last_seq_number, - }; - - let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); - - let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - - let send_result = websocket_tx - .lock() - .await - .send(MessageCapable::from_str(msg.to_string().as_str())) - .await; - if send_result.is_err() { - // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); - break; - } - - last_heartbeat_timestamp = time::Instant::now(); - last_heartbeat_acknowledged = false; - } - } - } - - fn new( - heartbeat_interval: Duration, - websocket_tx: Arc>>, - kill_rc: tokio::sync::broadcast::Receiver<()>, - ) -> HeartbeatHandler { - let (send, receive) = tokio::sync::mpsc::channel(32); - let kill_receive = kill_rc.resubscribe(); - - let handle: JoinHandle<()> = task::spawn(async move { - HeartbeatHandler::heartbeat_task( - websocket_tx, - heartbeat_interval, - receive, - kill_receive, - ) - .await; - }); - - Self { - heartbeat_interval, - send, - handle, - hb_type: (PhantomData::, PhantomData::), - } - } -} diff --git a/src/instance.rs b/src/instance.rs index 8c462a6..4ce4338 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -9,11 +9,11 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::GatewayCapable; +use crate::gateway::{Gateway, GatewayHandle}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, Limit, LimitType, User, UserSettings}; -use crate::{Gateway, GatewayHandle, UrlBundle}; +use crate::UrlBundle; #[derive(Debug, Clone, Default)] /// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server. diff --git a/src/lib.rs b/src/lib.rs index 510a727..47bbaab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,23 +17,6 @@ #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); -#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] -pub type Gateway = DefaultGateway; -#[cfg(all(target_arch = "wasm32", feature = "client"))] -pub type Gateway = WasmGateway; -#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] -pub type GatewayHandle = DefaultGatewayHandle; -#[cfg(all(target_arch = "wasm32", feature = "client"))] -pub type GatewayHandle = WasmGatewayHandle; - -#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] -use gateway::DefaultGateway; -#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] -use gateway::DefaultGatewayHandle; -#[cfg(all(target_arch = "wasm32", feature = "client"))] -use gateway::WasmGateway; -#[cfg(all(target_arch = "wasm32", feature = "client"))] -use gateway::WasmGatewayHandle; use url::{ParseError, Url}; #[cfg(feature = "client")] diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 1d1c58c..5acf2ae 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -12,7 +12,10 @@ use crate::types::{ }; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::types::Composite; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index 8c0b8e6..b3916e2 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -7,7 +7,10 @@ use crate::types::entities::User; use crate::types::Snowflake; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::gateway::GatewayHandle; + +#[cfg(feature = "client")] +use crate::types::Composite; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 8638ff7..eb04322 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -16,7 +16,7 @@ use crate::types::{ use super::PublicUser; #[cfg(feature = "client")] -use crate::{gateway::Updateable, GatewayHandle}; +use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; @@ -24,6 +24,9 @@ use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; #[cfg(feature = "client")] use crate::types::Composite; +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; + /// See #[derive(Serialize, Deserialize, Debug, Default, Clone)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 3371598..8343628 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -24,7 +24,10 @@ pub use voice_state::*; pub use webhook::*; #[cfg(feature = "client")] -use crate::{gateway::Updateable, GatewayHandle}; +use crate::gateway::Updateable; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; #[cfg(feature = "client")] use async_trait::async_trait; diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 1166431..6a8327e 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -12,7 +12,10 @@ use chorus_macros::{Composite, Updateable}; use crate::gateway::Updateable; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::types::Composite; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index e247240..a7bdf63 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -8,7 +8,10 @@ use std::fmt::Debug; use crate::gateway::Updateable; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::types::Composite; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; #[cfg(feature = "client")] use chorus_macros::{Composite, Updateable}; diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 3f122d6..1a0c3b3 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -4,7 +4,10 @@ use std::sync::{Arc, RwLock}; use chorus_macros::Composite; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::types::Composite; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index 3d8c687..cf5716c 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -10,7 +10,10 @@ use crate::gateway::Updateable; use chorus_macros::{Composite, Updateable}; #[cfg(feature = "client")] -use crate::{types::Composite, GatewayHandle}; +use crate::types::Composite; + +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; use crate::types::{ entities::{Guild, User}, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 76f20c7..b533fd2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; -use chorus::gateway::{DefaultGateway, GatewayCapable}; +use chorus::gateway::Gateway; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -43,7 +43,7 @@ impl TestBundle { limits: self.user.limits.clone(), settings: self.user.settings.clone(), object: self.user.object.clone(), - gateway: DefaultGateway::spawn(self.instance.urls.wss.clone()) + gateway: Gateway::spawn(self.instance.urls.wss.clone()) .await .unwrap(), } diff --git a/tests/gateway.rs b/tests/gateway.rs index 2303707..0b1e12f 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,17 +2,15 @@ mod common; use std::sync::{Arc, RwLock}; +use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; -use chorus::{gateway::*, GatewayHandle}; #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { let bundle = common::setup().await; - let _: GatewayHandle = DefaultGateway::spawn(bundle.urls.wss.clone()) - .await - .unwrap(); + let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); common::teardown(bundle).await } @@ -21,9 +19,7 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway: GatewayHandle = DefaultGateway::spawn(bundle.urls.wss.clone()) - .await - .unwrap(); + let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); let mut identify = types::GatewayIdentifyPayload::common(); identify.token = bundle.user.token.clone(); From b3e7876ccfab7aff150f28162483b9380d8cddc7 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 19:18:25 +0100 Subject: [PATCH 072/130] feature lock backend_tungstenite --- src/gateway/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 269a274..8314999 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; +#[cfg(not(target_arch = "wasm32"))] pub mod backend_tungstenite; pub mod events; pub mod gateway; From 6fd6bdcbbce220cb9aa1e17f0277cd049f3222b9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 21:15:10 +0100 Subject: [PATCH 073/130] Give tungstenite types distinct names --- src/gateway/backend_tungstenite.rs | 11 ++++++----- src/gateway/gateway.rs | 6 +++--- src/gateway/handle.rs | 2 +- src/gateway/heartbeat.rs | 4 ++-- src/gateway/mod.rs | 6 +++--- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backend_tungstenite.rs index 53b6982..f99424d 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backend_tungstenite.rs @@ -11,16 +11,17 @@ use super::GatewayMessage; use crate::errors::GatewayError; #[derive(Debug, Clone)] -pub struct WebSocketBackend; +pub struct TungsteniteBackend; // These could be made into inherent associated types when that's stabilized -pub type WsSink = SplitSink>, tungstenite::Message>; -pub type WsStream = SplitStream>>; +pub type TungsteniteSink = + SplitSink>, tungstenite::Message>; +pub type TungsteniteStream = SplitStream>>; -impl WebSocketBackend { +impl TungsteniteBackend { pub async fn connect( websocket_url: &str, - ) -> Result<(WsSink, WsStream), crate::errors::GatewayError> { + ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 9e3410c..e2923c9 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -6,7 +6,7 @@ use tokio::task; use self::event::Events; use super::*; -use super::{WsSink, WsStream}; +use super::{Sink, Stream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, @@ -17,8 +17,8 @@ use crate::types::{ pub struct Gateway { events: Arc>, heartbeat_handler: HeartbeatHandler, - websocket_send: Arc>, - websocket_receive: WsStream, + websocket_send: Arc>, + websocket_receive: Stream, kill_send: tokio::sync::broadcast::Sender<()>, store: Arc>>>>, url: String, diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 9a3c509..620faba 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -14,7 +14,7 @@ use crate::types::{self, Composite}; pub struct GatewayHandle { pub url: String, pub events: Arc>, - pub websocket_send: Arc>, + pub websocket_send: Arc>, /// Tells gateway tasks to close pub(super) kill_send: tokio::sync::broadcast::Sender<()>, pub(crate) store: Arc>>>>, diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index a5875a4..b8e4bec 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -27,7 +27,7 @@ pub(super) struct HeartbeatHandler { impl HeartbeatHandler { pub fn new( heartbeat_interval: Duration, - websocket_tx: Arc>, + websocket_tx: Arc>, kill_rc: tokio::sync::broadcast::Receiver<()>, ) -> Self { let (send, receive) = tokio::sync::mpsc::channel(32); @@ -49,7 +49,7 @@ impl HeartbeatHandler { /// Can be killed by the kill broadcast; /// If the websocket is closed, will die out next time it tries to send a heartbeat; pub async fn heartbeat_task( - websocket_tx: Arc>, + websocket_tx: Arc>, heartbeat_interval: Duration, mut receive: Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 8314999..03ac502 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -23,11 +23,11 @@ use std::sync::{Arc, RwLock}; use tokio::sync::Mutex; #[cfg(not(target_arch = "wasm32"))] -pub type WsSink = backend_tungstenite::WsSink; +pub type Sink = backend_tungstenite::TungsteniteSink; #[cfg(not(target_arch = "wasm32"))] -pub type WsStream = backend_tungstenite::WsStream; +pub type Stream = backend_tungstenite::TungsteniteStream; #[cfg(not(target_arch = "wasm32"))] -pub type WebSocketBackend = backend_tungstenite::WebSocketBackend; +pub type WebSocketBackend = backend_tungstenite::TungsteniteBackend; // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] From 73342d5dd74ce688aaba3e5a737395be2239da06 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 21:21:34 +0100 Subject: [PATCH 074/130] reorganize files --- src/gateway/backends/mod.rs | 9 +++++++++ .../tungstenite.rs} | 2 +- src/gateway/mod.rs | 11 ++--------- 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 src/gateway/backends/mod.rs rename src/gateway/{backend_tungstenite.rs => backends/tungstenite.rs} (98%) diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs new file mode 100644 index 0000000..53123fe --- /dev/null +++ b/src/gateway/backends/mod.rs @@ -0,0 +1,9 @@ +#[cfg(not(target_arch = "wasm32"))] +pub mod tungstenite; + +#[cfg(not(target_arch = "wasm32"))] +pub type Sink = tungstenite::TungsteniteSink; +#[cfg(not(target_arch = "wasm32"))] +pub type Stream = tungstenite::TungsteniteStream; +#[cfg(not(target_arch = "wasm32"))] +pub type WebSocketBackend = tungstenite::TungsteniteBackend; diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backends/tungstenite.rs similarity index 98% rename from src/gateway/backend_tungstenite.rs rename to src/gateway/backends/tungstenite.rs index f99424d..5184329 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -7,8 +7,8 @@ use tokio_tungstenite::{ connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, }; -use super::GatewayMessage; use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; #[derive(Debug, Clone)] pub struct TungsteniteBackend; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 03ac502..076ed54 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,13 +1,13 @@ use async_trait::async_trait; -#[cfg(not(target_arch = "wasm32"))] -pub mod backend_tungstenite; +pub mod backends; pub mod events; pub mod gateway; pub mod handle; pub mod heartbeat; pub mod message; +pub use backends::*; pub use gateway::*; pub use handle::*; use heartbeat::*; @@ -22,13 +22,6 @@ use std::sync::{Arc, RwLock}; use tokio::sync::Mutex; -#[cfg(not(target_arch = "wasm32"))] -pub type Sink = backend_tungstenite::TungsteniteSink; -#[cfg(not(target_arch = "wasm32"))] -pub type Stream = backend_tungstenite::TungsteniteStream; -#[cfg(not(target_arch = "wasm32"))] -pub type WebSocketBackend = backend_tungstenite::TungsteniteBackend; - // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] const GATEWAY_DISPATCH: u8 = 0; From 8f0d8813eb228f4c60715e4f5496e014aa2c8323 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 22:04:18 +0100 Subject: [PATCH 075/130] Better feature locking, add wasm.rs --- src/gateway/backends/mod.rs | 21 ++++++++++++---- src/gateway/backends/wasm.rs | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 src/gateway/backends/wasm.rs diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs index 53123fe..fe6e325 100644 --- a/src/gateway/backends/mod.rs +++ b/src/gateway/backends/mod.rs @@ -1,9 +1,22 @@ -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub mod tungstenite; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub use tungstenite::*; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub mod wasm; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub use wasm::*; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type Sink = tungstenite::TungsteniteSink; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type Stream = tungstenite::TungsteniteStream; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub type WebSocketBackend = tungstenite::TungsteniteBackend; + +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Sink = wasm::WasmSink; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Stream = wasm::WasmStream; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs new file mode 100644 index 0000000..546a05d --- /dev/null +++ b/src/gateway/backends/wasm.rs @@ -0,0 +1,46 @@ +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; + +use ws_stream_wasm::*; + +use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; + +#[derive(Debug, Clone)] +pub struct WasmBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WasmSink = SplitSink>, tungstenite::Message>; +pub type WasmStream = SplitStream>>; + +impl WasmBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { + let (websocket_stream, _) = match WsMeta::connect(); + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(GatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + Ok(websocket_stream.split()) + } +} + +impl From for WsMessage { + fn from(message: GatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for GatewayMessage { + fn from(value: WsMessage) -> Self { + Self(value.to_string()) + } +} From 1203e20358319912303aab40fd2d8b1362c0ec6a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 22:23:24 +0100 Subject: [PATCH 076/130] Implement wasm Backend --- src/gateway/backends/mod.rs | 1 + src/gateway/backends/wasm.rs | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs index fe6e325..edb5dc9 100644 --- a/src/gateway/backends/mod.rs +++ b/src/gateway/backends/mod.rs @@ -2,6 +2,7 @@ pub mod tungstenite; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub use tungstenite::*; + #[cfg(all(target_arch = "wasm32", feature = "client"))] pub mod wasm; #[cfg(all(target_arch = "wasm32", feature = "client"))] diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs index 546a05d..e9927ac 100644 --- a/src/gateway/backends/wasm.rs +++ b/src/gateway/backends/wasm.rs @@ -12,22 +12,19 @@ use crate::gateway::GatewayMessage; pub struct WasmBackend; // These could be made into inherent associated types when that's stabilized -pub type WasmSink = SplitSink>, tungstenite::Message>; -pub type WasmStream = SplitStream>>; +pub type WasmSink = SplitSink; +pub type WasmStream = SplitStream; impl WasmBackend { pub async fn connect( websocket_url: &str, ) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { - let (websocket_stream, _) = match WsMeta::connect(); - { - Ok(websocket_stream) => websocket_stream, - Err(e) => { - return Err(GatewayError::CannotConnect { - error: e.to_string(), - }) - } - }; + let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { + Ok(stream) => Ok(stream), + Err(e) => Err(GatewayError::CannotConnect { + error: e.to_string(), + }), + }?; Ok(websocket_stream.split()) } @@ -41,6 +38,13 @@ impl From for WsMessage { impl From for GatewayMessage { fn from(value: WsMessage) -> Self { - Self(value.to_string()) + match value { + WsMessage::Text(text) => Self(text), + WsMessage::Binary(bin) => { + let mut text = String::new(); + let _ = bin.iter().map(|v| text.push_str(&v.to_string())); + Self(text) + } + } } } From 5826ccd9232ca4f2bf15136a1dda2f036694d776 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 22:49:41 +0100 Subject: [PATCH 077/130] add wasm-bindgen-test --- Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++--- tests/wasm.rs | 0 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index f7379c1..ad4ce35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "url", + "wasm-bindgen-test", "ws_stream_wasm", ] @@ -252,6 +253,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -1674,6 +1685,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2627,6 +2644,31 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "web-sys" version = "0.3.65" diff --git a/Cargo.toml b/Cargo.toml index 72028c6..31de1b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,10 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -# TODO: Remove the below 2 imports for production! +# TODO: Remove the below 3 imports for production! ws_stream_wasm = "0.7.4" pharos = "0.5.3" +wasm-bindgen-test = "0.3.38" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" @@ -70,9 +71,8 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } -tokio-tungstenite = { version = "0.20.1", default-features = false } ws_stream_wasm = "0.7.4" -pharos = "0.5.3" +wasm-bindgen-test = "0.3.38" [dev-dependencies] diff --git a/tests/wasm.rs b/tests/wasm.rs new file mode 100644 index 0000000..e69de29 From 5fdac6d48f87683dfcadc11aa9cfa9b8f14feb0c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 23:04:25 +0100 Subject: [PATCH 078/130] Build & Test for wasm --- .github/workflows/build_and_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 31962c2..60ef223 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -44,4 +44,6 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" From 505abdb82dd9444cc36bb5250932fd4a4b2934cf Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 23:15:12 +0100 Subject: [PATCH 079/130] Add macos safari wasm test --- .github/workflows/build_and_test.yml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 60ef223..e798ec6 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -10,7 +10,7 @@ env: CARGO_TERM_COLOR: always jobs: - rust: + linux: runs-on: ubuntu-latest @@ -33,6 +33,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + prefix-key: "linux" - name: Build, Test and Publish Coverage run: | if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then @@ -46,4 +47,27 @@ jobs: fi curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" - + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Build, Test wasm with Safari + run: SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From a00ec555d011d6d92527a2d36e1bfa54ce8e6090 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 23:18:58 +0100 Subject: [PATCH 080/130] Add wasm32 target --- .github/workflows/build_and_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index e798ec6..aedb98d 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -36,6 +36,7 @@ jobs: prefix-key: "linux" - name: Build, Test and Publish Coverage run: | + rustup target add wasm32-unknown-unknown if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force @@ -70,4 +71,6 @@ jobs: cache-all-crates: "true" prefix-key: "macos" - name: Build, Test wasm with Safari - run: SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file + run: | + rustup target add wasm32-unknown-unknown + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From 4023d023ede9c59d4af470a43f5384d905f6de58 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:06:35 +0100 Subject: [PATCH 081/130] Add wasm.rs test --- tests/wasm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wasm.rs b/tests/wasm.rs index e69de29..2250965 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -0,0 +1 @@ +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); From dd2b29622f807f79d513cb29f77367aec2925706 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:07:09 +0100 Subject: [PATCH 082/130] Move wasm-pack installation before test execution --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index aedb98d..df8f52b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -37,6 +37,7 @@ jobs: - name: Build, Test and Publish Coverage run: | rustup target add wasm32-unknown-unknown + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force @@ -46,7 +47,6 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" macos: runs-on: macos-latest From 7956a0e3cb998e841d9e2d381b694e42ce6dc157 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:07:16 +0100 Subject: [PATCH 083/130] Fix build on wasm32 --- src/gateway/gateway.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index e2923c9..d10dbfb 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -37,7 +37,10 @@ impl Gateway { // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread + #[cfg(not(target_arch = "wasm32"))] let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + #[cfg(target_arch = "wasm32")] + let msg: GatewayMessage = websocket_receive.next().await.unwrap().into(); let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { @@ -91,11 +94,18 @@ impl Gateway { loop { let msg = self.websocket_receive.next().await; + // PRETTYFYME: Remove inline conditional compiling // This if chain can be much better but if let is unstable on stable rust + #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { self.handle_message(message.into()).await; continue; } + #[cfg(target_arch = "wasm32")] + if let Some(message) = msg { + self.handle_message(message.into()).await; + continue; + } // We couldn't receive the next message or it was an error, something is wrong with the websocket, close warn!("GW: Websocket is broken, stopping gateway"); From c82b02047f7ef2646713f4f75e0f47f11e89c7b2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:13:32 +0100 Subject: [PATCH 084/130] Fix examples depending on tokio::time --- examples/gateway_observers.rs | 5 +++-- examples/gateway_simple.rs | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index d4e690c..a13c935 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -6,7 +6,7 @@ use chorus::{ types::{GatewayIdentifyPayload, GatewayReady}, }; use std::{sync::Arc, time::Duration}; -use tokio::{self, time::sleep}; +use tokio::{self}; // This example creates a simple gateway connection and a basic observer struct @@ -54,9 +54,10 @@ async fn main() { let mut identify = GatewayIdentifyPayload::common(); identify.token = token; gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index a9c019b..acb4eb3 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -2,7 +2,6 @@ use std::time::Duration; use chorus::gateway::Gateway; use chorus::{self, types::GatewayIdentifyPayload}; -use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event #[tokio::main(flavor = "current_thread")] @@ -27,10 +26,10 @@ async fn main() { identify.token = token; // Send off the event - gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } From af7c4116c65033bfacd137dad0b081c4e0553c8b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:25:18 +0100 Subject: [PATCH 085/130] fix clippy warn --- examples/gateway_simple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index acb4eb3..2996283 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -10,7 +10,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated From 0b5834762868d85c804c6596004ab0ed1e9a8f63 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 00:27:27 +0100 Subject: [PATCH 086/130] Add example wasm bindgen test --- tests/wasm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/wasm.rs b/tests/wasm.rs index 2250965..d5d26c1 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1 +1,7 @@ +use wasm_bindgen_test::wasm_bindgen_test; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[wasm_bindgen_test] +fn pass() { + let _ = String::new(); +} From 8c385ee39ee07e6a133022f094254b73d1e4670d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:41:32 +0100 Subject: [PATCH 087/130] Add wasm-bindgen to Cargo.toml --- Cargo.lock | 2 +- Cargo.toml | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad4ce35..7c74e2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,6 @@ dependencies = [ "lazy_static", "log", "native-tls", - "pharos", "poem", "rand", "regex", @@ -223,6 +222,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "url", + "wasm-bindgen", "wasm-bindgen-test", "ws_stream_wasm", ] diff --git a/Cargo.toml b/Cargo.toml index 31de1b1..24a7114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,10 +54,6 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -# TODO: Remove the below 3 imports for production! -ws_stream_wasm = "0.7.4" -pharos = "0.5.3" -wasm-bindgen-test = "0.3.38" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" @@ -72,8 +68,9 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } ws_stream_wasm = "0.7.4" -wasm-bindgen-test = "0.3.38" [dev-dependencies] lazy_static = "1.4.0" +wasm-bindgen-test = "0.3.38" +wasm-bindgen = "0.2.88" From d0a5f194affb48e805e20bca33480235d212b5d6 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:41:46 +0100 Subject: [PATCH 088/130] Add wasm test configuration --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ec2f3b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' From b7eeb0dc8c98786a6bd1e0429398f1f6adb799bc Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:42:07 +0100 Subject: [PATCH 089/130] Install wasm-bindgen-cli on linux --- .github/workflows/build_and_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index df8f52b..c57dc24 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -40,6 +40,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --vers "0.2.88" --force cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else From 18abf66f448d5fe30883bf057469369dd08c1eab Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:43:08 +0100 Subject: [PATCH 090/130] Add wasm-bindgen-cli to macos --- .github/workflows/build_and_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c57dc24..157861c 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -74,4 +74,6 @@ jobs: - name: Build, Test wasm with Safari run: | rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --vers "0.2.88" --force SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From e142fec9f685ae10e81fe0ccdabeb80ef909eb13 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:46:10 +0100 Subject: [PATCH 091/130] Correct "vers" to "version" --- .github/workflows/build_and_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 157861c..127b088 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -40,7 +40,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --vers "0.2.88" --force + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else @@ -75,5 +75,5 @@ jobs: run: | rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --vers "0.2.88" --force + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From 10adb12298d3693a2ce9785daf24558775d469f8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:57:31 +0100 Subject: [PATCH 092/130] Attempt to locate correct geckodriver --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 127b088..e7ef766 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -48,7 +48,7 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" macos: runs-on: macos-latest steps: From f26d0474ac88d016e70d6227531279ba26d5712a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 11:59:37 +0100 Subject: [PATCH 093/130] Run wasm tests first --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index e7ef766..3021c97 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -38,6 +38,7 @@ jobs: run: | rustup target add wasm32-unknown-unknown curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + GECKODRIVER=$(which geckodriver) CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force @@ -48,7 +49,6 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - GECKODRIVER=$(which geckodriver) CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" macos: runs-on: macos-latest steps: From 81289ff92b11c394c076accb00978c956b28c61f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:04:56 +0100 Subject: [PATCH 094/130] maybe this will fix ci :clueless: --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 3021c97..0679775 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -38,7 +38,7 @@ jobs: run: | rustup target add wasm32-unknown-unknown curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - GECKODRIVER=$(which geckodriver) CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 sudo cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force From 47ecd5ae9fb1ee3b8a64ec297acc030808ba79fe Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:08:40 +0100 Subject: [PATCH 095/130] Move wasm-bindgen-cli install --- .github/workflows/build_and_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0679775..1079a13 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -38,10 +38,10 @@ jobs: run: | rustup target add wasm32-unknown-unknown curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 sudo cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else From 5c48cdfb5e7f008846d7880776f9522d5f3342e7 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:11:30 +0100 Subject: [PATCH 096/130] Add cargo-binstall installation script for wasm-bindgen-cli --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 1079a13..2feb908 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -38,10 +38,10 @@ jobs: run: | rustup target add wasm32-unknown-unknown curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else From 0f09cb8fb15c518592f7759cb60d7b74ade78454 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:29:20 +0100 Subject: [PATCH 097/130] Try using only one browser --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 2feb908..eac5119 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -40,7 +40,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - GECKODRIVER=/usr/local/share/gecko_driver CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=/usr/local/share/gecko_driver cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 From ebd64f0e25dffc7d14ed990ae4bcce3753d3f0be Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:32:48 +0100 Subject: [PATCH 098/130] remove geckodriver --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index eac5119..b7a643b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -40,7 +40,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - GECKODRIVER=/usr/local/share/gecko_driver cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 From e73ad1d965c2d0b208abd39d64e19a0781e54695 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:38:39 +0100 Subject: [PATCH 099/130] Move all wasm related tests to macos --- .github/workflows/build_and_test.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index b7a643b..0010a13 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -36,12 +36,8 @@ jobs: prefix-key: "linux" - name: Build, Test and Publish Coverage run: | - rustup target add wasm32-unknown-unknown - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - CHROMEDRIVER=/usr/local/share/chromedriver-linux64 cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else @@ -76,4 +72,6 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From ced83c2a57b529cb874fe174621f99f12c949118 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 12:51:55 +0100 Subject: [PATCH 100/130] Rename macOS test step for clarity --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0010a13..c0e314f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -67,7 +67,7 @@ jobs: with: cache-all-crates: "true" prefix-key: "macos" - - name: Build, Test wasm with Safari + - name: Run WASM tests with Safari, Firefox, Chrome run: | rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash From d34a813d8afd2599300616643bedd5918da735d5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 13:16:49 +0100 Subject: [PATCH 101/130] Try out combined coverage report --- .github/workflows/build_and_test.yml | 34 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c0e314f..d523165 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -39,12 +39,18 @@ jobs: if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force - cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 + cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --timeout 120 --output-dir ./cargo/output-linux.lcov --out Lcov else echo "Code Coverage step is skipped on forks!" cargo build --verbose --all-features cargo test --verbose --all-features fi + - name: Upload coverage for Linux + if: ${{ secrets.COVERALLS_REPO_TOKEN }} + uses: actions/upload-artifact@v2 + with: + name: coverage-linux + path: ./cargo/output-linux.lcov macos: runs-on: macos-latest steps: @@ -72,6 +78,26 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" - GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" - CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file + if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then + cargo binstall --no-confirm cargo-tarpaulin --force + SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo tarpaulin --target wasm32-unknown-unknown --no-default-features --features="client, rt" --avoid-cfg-tarpaulin --tests --verbose --skip-clean --timeout 120 --output-dir ./cargo/output-macos.lcov --out Lcov + else + echo "Code Coverage step is skipped on forks!" + SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + fi + - name: Upload coverage for macOS + if: ${{ secrets.COVERALLS_REPO_TOKEN }} + uses: actions/upload-artifact@v2 + with: + name: coverage-macos + path: ./cargo/output-macos.lcov + + upload-coverage: + needs: [linux, macos] + if: ${{ secrets.COVERALLS_REPO_TOKEN }} + runs-on: ubuntu-latest + steps: + - name: Download all workflow run artifacts + uses: actions/download-artifact@v2 + - name: Upload coverage to Coveralls.io + uses: coverallsapp/github-action@v2 \ No newline at end of file From fb46ab83ac08cc1040153ce4dec9386387379db2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 13:22:23 +0100 Subject: [PATCH 102/130] try different strategy to skip coverage on forks --- .github/workflows/build_and_test.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d523165..05b0acb 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,6 +15,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Set coverage flag + id: cov-flag + run: echo ::set-output name=flag::${{ github.event.pull_request.head.repo.fork == false }} - uses: actions/checkout@v4 - name: Clone spacebar server run: | @@ -46,7 +49,7 @@ jobs: cargo test --verbose --all-features fi - name: Upload coverage for Linux - if: ${{ secrets.COVERALLS_REPO_TOKEN }} + if: steps.cov-flag.outputs.flag == 'true' uses: actions/upload-artifact@v2 with: name: coverage-linux @@ -54,6 +57,9 @@ jobs: macos: runs-on: macos-latest steps: + - name: Set coverage flag + id: cov-flag + run: echo ::set-output name=flag::${{ github.event.pull_request.head.repo.fork == false }} - uses: actions/checkout@v4 - name: Clone spacebar server run: | @@ -86,7 +92,7 @@ jobs: SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" fi - name: Upload coverage for macOS - if: ${{ secrets.COVERALLS_REPO_TOKEN }} + if: steps.cov-flag.outputs.flag == 'true' uses: actions/upload-artifact@v2 with: name: coverage-macos @@ -94,7 +100,7 @@ jobs: upload-coverage: needs: [linux, macos] - if: ${{ secrets.COVERALLS_REPO_TOKEN }} + if: needs.linux.outputs.flag == 'true' && needs.macos.outputs.flag == 'true' runs-on: ubuntu-latest steps: - name: Download all workflow run artifacts From 07c8c164963625bcfc725a8924fc5e3ce8b2215a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 13:33:40 +0100 Subject: [PATCH 103/130] Revert "try different strategy to skip coverage on forks" This reverts commit fb46ab83ac08cc1040153ce4dec9386387379db2. --- .github/workflows/build_and_test.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 05b0acb..d523165 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,9 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set coverage flag - id: cov-flag - run: echo ::set-output name=flag::${{ github.event.pull_request.head.repo.fork == false }} - uses: actions/checkout@v4 - name: Clone spacebar server run: | @@ -49,7 +46,7 @@ jobs: cargo test --verbose --all-features fi - name: Upload coverage for Linux - if: steps.cov-flag.outputs.flag == 'true' + if: ${{ secrets.COVERALLS_REPO_TOKEN }} uses: actions/upload-artifact@v2 with: name: coverage-linux @@ -57,9 +54,6 @@ jobs: macos: runs-on: macos-latest steps: - - name: Set coverage flag - id: cov-flag - run: echo ::set-output name=flag::${{ github.event.pull_request.head.repo.fork == false }} - uses: actions/checkout@v4 - name: Clone spacebar server run: | @@ -92,7 +86,7 @@ jobs: SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" fi - name: Upload coverage for macOS - if: steps.cov-flag.outputs.flag == 'true' + if: ${{ secrets.COVERALLS_REPO_TOKEN }} uses: actions/upload-artifact@v2 with: name: coverage-macos @@ -100,7 +94,7 @@ jobs: upload-coverage: needs: [linux, macos] - if: needs.linux.outputs.flag == 'true' && needs.macos.outputs.flag == 'true' + if: ${{ secrets.COVERALLS_REPO_TOKEN }} runs-on: ubuntu-latest steps: - name: Download all workflow run artifacts From c7ff1724e6f60b2b5cc2e90f25b5105d47d8717a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 13:33:45 +0100 Subject: [PATCH 104/130] Revert "Try out combined coverage report" This reverts commit d34a813d8afd2599300616643bedd5918da735d5. --- .github/workflows/build_and_test.yml | 34 ++++------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d523165..c0e314f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -39,18 +39,12 @@ jobs: if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm cargo-tarpaulin --force - cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --timeout 120 --output-dir ./cargo/output-linux.lcov --out Lcov + cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 else echo "Code Coverage step is skipped on forks!" cargo build --verbose --all-features cargo test --verbose --all-features fi - - name: Upload coverage for Linux - if: ${{ secrets.COVERALLS_REPO_TOKEN }} - uses: actions/upload-artifact@v2 - with: - name: coverage-linux - path: ./cargo/output-linux.lcov macos: runs-on: macos-latest steps: @@ -78,26 +72,6 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then - cargo binstall --no-confirm cargo-tarpaulin --force - SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo tarpaulin --target wasm32-unknown-unknown --no-default-features --features="client, rt" --avoid-cfg-tarpaulin --tests --verbose --skip-clean --timeout 120 --output-dir ./cargo/output-macos.lcov --out Lcov - else - echo "Code Coverage step is skipped on forks!" - SAFARIDRIVER=$(which safaridriver) CHROMEDRIVER=$(which chromedriver) GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" - fi - - name: Upload coverage for macOS - if: ${{ secrets.COVERALLS_REPO_TOKEN }} - uses: actions/upload-artifact@v2 - with: - name: coverage-macos - path: ./cargo/output-macos.lcov - - upload-coverage: - needs: [linux, macos] - if: ${{ secrets.COVERALLS_REPO_TOKEN }} - runs-on: ubuntu-latest - steps: - - name: Download all workflow run artifacts - uses: actions/download-artifact@v2 - - name: Upload coverage to Coveralls.io - uses: coverallsapp/github-action@v2 \ No newline at end of file + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From 06f3046134b13c223af97b8b5b70906135901b8a Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:40:55 +0100 Subject: [PATCH 105/130] initial wasm32 'support' (#443) * Give tungstenite types distinct names * reorganize files * Better feature locking, add wasm.rs * Implement wasm Backend * add wasm-bindgen-test * Build & Test for wasm * Add macos safari wasm test * Add wasm32 target * Add wasm.rs test * Move wasm-pack installation before test execution * Fix build on wasm32 * Fix examples depending on tokio::time * fix clippy warn * Add example wasm bindgen test * Add wasm-bindgen to Cargo.toml * Add wasm test configuration * Install wasm-bindgen-cli on linux * Add wasm-bindgen-cli to macos * Correct "vers" to "version" * Attempt to locate correct geckodriver * Run wasm tests first * maybe this will fix ci :clueless: * Move wasm-bindgen-cli install * Add cargo-binstall installation script for wasm-bindgen-cli * Try using only one browser * remove geckodriver * Move all wasm related tests to macos * Rename macOS test step for clarity * Try out combined coverage report * try different strategy to skip coverage on forks * Revert "try different strategy to skip coverage on forks" This reverts commit fb46ab83ac08cc1040153ce4dec9386387379db2. * Revert "Try out combined coverage report" This reverts commit d34a813d8afd2599300616643bedd5918da735d5. --- .cargo/config.toml | 2 + .github/workflows/build_and_test.yml | 34 ++++++++++++- Cargo.lock | 44 +++++++++++++++- Cargo.toml | 7 +-- examples/gateway_observers.rs | 5 +- examples/gateway_simple.rs | 7 ++- src/gateway/backends/mod.rs | 23 +++++++++ .../tungstenite.rs} | 13 ++--- src/gateway/backends/wasm.rs | 50 +++++++++++++++++++ src/gateway/gateway.rs | 16 ++++-- src/gateway/handle.rs | 2 +- src/gateway/heartbeat.rs | 4 +- src/gateway/mod.rs | 11 +--- tests/wasm.rs | 7 +++ 14 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 src/gateway/backends/mod.rs rename src/gateway/{backend_tungstenite.rs => backends/tungstenite.rs} (81%) create mode 100644 src/gateway/backends/wasm.rs create mode 100644 tests/wasm.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ec2f3b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 31962c2..c0e314f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -10,7 +10,7 @@ env: CARGO_TERM_COLOR: always jobs: - rust: + linux: runs-on: ubuntu-latest @@ -33,6 +33,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + prefix-key: "linux" - name: Build, Test and Publish Coverage run: | if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then @@ -44,4 +45,33 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f7379c1..7c74e2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,6 @@ dependencies = [ "lazy_static", "log", "native-tls", - "pharos", "poem", "rand", "regex", @@ -223,6 +222,8 @@ dependencies = [ "tokio", "tokio-tungstenite", "url", + "wasm-bindgen", + "wasm-bindgen-test", "ws_stream_wasm", ] @@ -252,6 +253,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -1674,6 +1685,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2627,6 +2644,31 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "web-sys" version = "0.3.65" diff --git a/Cargo.toml b/Cargo.toml index 72028c6..24a7114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,6 @@ sqlx = { version = "0.7.1", features = [ ], optional = true } safina-timer = "0.1.11" rand = "0.8.5" -# TODO: Remove the below 2 imports for production! -ws_stream_wasm = "0.7.4" -pharos = "0.5.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.21.8" @@ -70,10 +67,10 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } -tokio-tungstenite = { version = "0.20.1", default-features = false } ws_stream_wasm = "0.7.4" -pharos = "0.5.3" [dev-dependencies] lazy_static = "1.4.0" +wasm-bindgen-test = "0.3.38" +wasm-bindgen = "0.2.88" diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index d4e690c..a13c935 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -6,7 +6,7 @@ use chorus::{ types::{GatewayIdentifyPayload, GatewayReady}, }; use std::{sync::Arc, time::Duration}; -use tokio::{self, time::sleep}; +use tokio::{self}; // This example creates a simple gateway connection and a basic observer struct @@ -54,9 +54,10 @@ async fn main() { let mut identify = GatewayIdentifyPayload::common(); identify.token = token; gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index a9c019b..2996283 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -2,7 +2,6 @@ use std::time::Duration; use chorus::gateway::Gateway; use chorus::{self, types::GatewayIdentifyPayload}; -use tokio::time::sleep; /// This example creates a simple gateway connection and a session with an Identify event #[tokio::main(flavor = "current_thread")] @@ -11,7 +10,7 @@ async fn main() { let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated @@ -27,10 +26,10 @@ async fn main() { identify.token = token; // Send off the event - gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs new file mode 100644 index 0000000..edb5dc9 --- /dev/null +++ b/src/gateway/backends/mod.rs @@ -0,0 +1,23 @@ +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub mod tungstenite; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub use tungstenite::*; + +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub mod wasm; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub use wasm::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type Sink = tungstenite::TungsteniteSink; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type Stream = tungstenite::TungsteniteStream; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type WebSocketBackend = tungstenite::TungsteniteBackend; + +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Sink = wasm::WasmSink; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Stream = wasm::WasmStream; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/gateway/backend_tungstenite.rs b/src/gateway/backends/tungstenite.rs similarity index 81% rename from src/gateway/backend_tungstenite.rs rename to src/gateway/backends/tungstenite.rs index 53b6982..5184329 100644 --- a/src/gateway/backend_tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -7,20 +7,21 @@ use tokio_tungstenite::{ connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, }; -use super::GatewayMessage; use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; #[derive(Debug, Clone)] -pub struct WebSocketBackend; +pub struct TungsteniteBackend; // These could be made into inherent associated types when that's stabilized -pub type WsSink = SplitSink>, tungstenite::Message>; -pub type WsStream = SplitStream>>; +pub type TungsteniteSink = + SplitSink>, tungstenite::Message>; +pub type TungsteniteStream = SplitStream>>; -impl WebSocketBackend { +impl TungsteniteBackend { pub async fn connect( websocket_url: &str, - ) -> Result<(WsSink, WsStream), crate::errors::GatewayError> { + ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { let mut roots = rustls::RootCertStore::empty(); for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs new file mode 100644 index 0000000..e9927ac --- /dev/null +++ b/src/gateway/backends/wasm.rs @@ -0,0 +1,50 @@ +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; + +use ws_stream_wasm::*; + +use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; + +#[derive(Debug, Clone)] +pub struct WasmBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WasmSink = SplitSink; +pub type WasmStream = SplitStream; + +impl WasmBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { + let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { + Ok(stream) => Ok(stream), + Err(e) => Err(GatewayError::CannotConnect { + error: e.to_string(), + }), + }?; + + Ok(websocket_stream.split()) + } +} + +impl From for WsMessage { + fn from(message: GatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for GatewayMessage { + fn from(value: WsMessage) -> Self { + match value { + WsMessage::Text(text) => Self(text), + WsMessage::Binary(bin) => { + let mut text = String::new(); + let _ = bin.iter().map(|v| text.push_str(&v.to_string())); + Self(text) + } + } + } +} diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 9e3410c..d10dbfb 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -6,7 +6,7 @@ use tokio::task; use self::event::Events; use super::*; -use super::{WsSink, WsStream}; +use super::{Sink, Stream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, @@ -17,8 +17,8 @@ use crate::types::{ pub struct Gateway { events: Arc>, heartbeat_handler: HeartbeatHandler, - websocket_send: Arc>, - websocket_receive: WsStream, + websocket_send: Arc>, + websocket_receive: Stream, kill_send: tokio::sync::broadcast::Sender<()>, store: Arc>>>>, url: String, @@ -37,7 +37,10 @@ impl Gateway { // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread + #[cfg(not(target_arch = "wasm32"))] let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + #[cfg(target_arch = "wasm32")] + let msg: GatewayMessage = websocket_receive.next().await.unwrap().into(); let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { @@ -91,11 +94,18 @@ impl Gateway { loop { let msg = self.websocket_receive.next().await; + // PRETTYFYME: Remove inline conditional compiling // This if chain can be much better but if let is unstable on stable rust + #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { self.handle_message(message.into()).await; continue; } + #[cfg(target_arch = "wasm32")] + if let Some(message) = msg { + self.handle_message(message.into()).await; + continue; + } // We couldn't receive the next message or it was an error, something is wrong with the websocket, close warn!("GW: Websocket is broken, stopping gateway"); diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 9a3c509..620faba 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -14,7 +14,7 @@ use crate::types::{self, Composite}; pub struct GatewayHandle { pub url: String, pub events: Arc>, - pub websocket_send: Arc>, + pub websocket_send: Arc>, /// Tells gateway tasks to close pub(super) kill_send: tokio::sync::broadcast::Sender<()>, pub(crate) store: Arc>>>>, diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index a5875a4..b8e4bec 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -27,7 +27,7 @@ pub(super) struct HeartbeatHandler { impl HeartbeatHandler { pub fn new( heartbeat_interval: Duration, - websocket_tx: Arc>, + websocket_tx: Arc>, kill_rc: tokio::sync::broadcast::Receiver<()>, ) -> Self { let (send, receive) = tokio::sync::mpsc::channel(32); @@ -49,7 +49,7 @@ impl HeartbeatHandler { /// Can be killed by the kill broadcast; /// If the websocket is closed, will die out next time it tries to send a heartbeat; pub async fn heartbeat_task( - websocket_tx: Arc>, + websocket_tx: Arc>, heartbeat_interval: Duration, mut receive: Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 8314999..076ed54 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,13 +1,13 @@ use async_trait::async_trait; -#[cfg(not(target_arch = "wasm32"))] -pub mod backend_tungstenite; +pub mod backends; pub mod events; pub mod gateway; pub mod handle; pub mod heartbeat; pub mod message; +pub use backends::*; pub use gateway::*; pub use handle::*; use heartbeat::*; @@ -22,13 +22,6 @@ use std::sync::{Arc, RwLock}; use tokio::sync::Mutex; -#[cfg(not(target_arch = "wasm32"))] -pub type WsSink = backend_tungstenite::WsSink; -#[cfg(not(target_arch = "wasm32"))] -pub type WsStream = backend_tungstenite::WsStream; -#[cfg(not(target_arch = "wasm32"))] -pub type WebSocketBackend = backend_tungstenite::WebSocketBackend; - // Gateway opcodes /// Opcode received when the server dispatches a [crate::types::WebSocketEvent] const GATEWAY_DISPATCH: u8 = 0; diff --git a/tests/wasm.rs b/tests/wasm.rs new file mode 100644 index 0000000..d5d26c1 --- /dev/null +++ b/tests/wasm.rs @@ -0,0 +1,7 @@ +use wasm_bindgen_test::wasm_bindgen_test; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[wasm_bindgen_test] +fn pass() { + let _ = String::new(); +} From bd7bb90baf4260ecc541a1874762f700d1c1504b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:03:06 +0100 Subject: [PATCH 106/130] Add tests for WASM --- tests/auth.rs | 12 ++++++++++++ tests/channels.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/gateway.rs | 29 +++++++++++++++++++++++++++++ tests/guilds.rs | 35 +++++++++++++++++++++++++++++++++++ tests/instance.rs | 11 +++++++++++ tests/invites.rs | 12 ++++++++++++ tests/members.rs | 11 +++++++++++ tests/messages.rs | 29 +++++++++++++++++++++++++++++ tests/relationships.rs | 29 +++++++++++++++++++++++++++++ tests/roles.rs | 17 +++++++++++++++++ 10 files changed, 221 insertions(+) diff --git a/tests/auth.rs b/tests/auth.rs index f89e5e4..4c79562 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,7 +1,13 @@ use chorus::types::RegisterSchema; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; +#[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_registration() { let bundle = common::setup().await; @@ -14,3 +20,9 @@ async fn test_registration() { bundle.instance.clone().register_account(reg).await.unwrap(); common::teardown(bundle).await; } + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_registration_wasm() { + test_registration().await +} diff --git a/tests/channels.rs b/tests/channels.rs index 1647652..d1b85b2 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -4,6 +4,42 @@ use chorus::types::{ }; mod common; +// PRETTYFYME: Move common wasm setup to common.rs + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_get_channel_wasm() { + get_channel().await +} +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_delete_channel_wasm() { + delete_channel().await +} +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_modify_channel_wasm() { + modify_channel().await +} +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_get_channel_messages_wasm() { + get_channel_messages().await +} +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_create_dm_wasm() { + create_dm().await +} +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_remove_add_person_from_to_dm_wasm() { + remove_add_person_from_to_dm().await +} #[tokio::test] async fn get_channel() { diff --git a/tests/gateway.rs b/tests/gateway.rs index 0b1e12f..a05a798 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -4,6 +4,35 @@ use std::sync::{Arc, RwLock}; use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_gateway_establish_wasm() { + test_gateway_establish().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_gateway_authenticate_wasm() { + test_gateway_authenticate().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_self_updating_structs_wasm() { + test_self_updating_structs().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_recursive_self_updating_structs_wasm() { + test_recursive_self_updating_structs().await +} #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; diff --git a/tests/guilds.rs b/tests/guilds.rs index d7e2699..2bafbab 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -3,6 +3,41 @@ use chorus::types::{ }; mod common; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn guild_creation_deletion_wasm() { + guild_creation_deletion().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn get_channels_wasm() { + get_channels().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn guild_create_ban_wasm() { + guild_create_ban().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn modify_guild_wasm() { + modify_guild().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn guild_remove_member_wasm() { + guild_remove_member().await +} #[tokio::test] async fn guild_creation_deletion() { diff --git a/tests/instance.rs b/tests/instance.rs index d3cd5f0..2466e60 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -1,4 +1,15 @@ mod common; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn guild_creation_deletion_wasm() { + guild_creation_deletion().await +} #[tokio::test] async fn generate_general_configuration_schema() { diff --git a/tests/invites.rs b/tests/invites.rs index ab264d4..a535b84 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,5 +1,17 @@ mod common; use chorus::types::CreateChannelInviteSchema; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn create_accept_invite_wasm() { + create_accept_invite().await +} + #[tokio::test] async fn create_accept_invite() { let mut bundle = common::setup().await; diff --git a/tests/members.rs b/tests/members.rs index fbab772..a2043ef 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -1,7 +1,18 @@ use chorus::{errors::ChorusResult, types::GuildMember}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn add_remove_role_wasm() { + add_remove_role().await.unwrap() +} + #[tokio::test] async fn add_remove_role() -> ChorusResult<()> { let mut bundle = common::setup().await; diff --git a/tests/messages.rs b/tests/messages.rs index 5ad9a89..fc46831 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -2,9 +2,38 @@ use std::fs::File; use std::io::{BufReader, Read}; use chorus::types::{self, Guild, Message, MessageSearchQuery}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn send_message_wasm() { + send_message().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn send_message_attachment_wasm() { + send_message_attachment().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn search_messages_wasm() { + search_messages().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_stickies_wasm() { + test_stickies().await +} + #[tokio::test] async fn send_message() { let mut bundle = common::setup().await; diff --git a/tests/relationships.rs b/tests/relationships.rs index 09ddab0..7f26e0f 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,7 +1,36 @@ use chorus::types::{self, Relationship, RelationshipType}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_get_relationships_wasm() { + test_get_relationships().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_get_mutual_relationships_wasm() { + test_get_mutual_relationships().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_modify_relationship_friends_wasm() { + test_modify_relationship_friends().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_modify_relationship_block_wasm() { + test_modify_relationship_block().await +} + #[tokio::test] async fn test_get_mutual_relationships() { let mut bundle = common::setup().await; diff --git a/tests/roles.rs b/tests/roles.rs index 8691138..0dd3b7f 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -1,7 +1,24 @@ use chorus::types::{self, RoleCreateModifySchema, RoleObject}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn create_and_get_roles_wasm() { + create_and_get_roles().await +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn get_and_delete_role_wasm() { + get_and_delete_role().await +} + #[tokio::test] async fn create_and_get_roles() { let mut bundle = common::setup().await; From 0cdb76bcf3ab2395815e3fa1fcd53fc9c5ec6620 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:11:56 +0100 Subject: [PATCH 107/130] remove all .await from wasm tests --- tests/auth.rs | 2 +- tests/channels.rs | 12 ++++++------ tests/gateway.rs | 8 ++++---- tests/guilds.rs | 10 +++++----- tests/instance.rs | 2 +- tests/invites.rs | 2 +- tests/members.rs | 2 +- tests/messages.rs | 8 ++++---- tests/relationships.rs | 8 ++++---- tests/roles.rs | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/auth.rs b/tests/auth.rs index 4c79562..b6731d7 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -24,5 +24,5 @@ async fn test_registration() { #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_registration_wasm() { - test_registration().await + test_registration() } diff --git a/tests/channels.rs b/tests/channels.rs index d1b85b2..270f64f 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -13,32 +13,32 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_channel_wasm() { - get_channel().await + get_channel() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_delete_channel_wasm() { - delete_channel().await + delete_channel() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_channel_wasm() { - modify_channel().await + modify_channel() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_channel_messages_wasm() { - get_channel_messages().await + get_channel_messages() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_create_dm_wasm() { - create_dm().await + create_dm() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_remove_add_person_from_to_dm_wasm() { - remove_add_person_from_to_dm().await + remove_add_person_from_to_dm() } #[tokio::test] diff --git a/tests/gateway.rs b/tests/gateway.rs index a05a798..54de74f 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -13,25 +13,25 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_gateway_establish_wasm() { - test_gateway_establish().await + test_gateway_establish() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_gateway_authenticate_wasm() { - test_gateway_authenticate().await + test_gateway_authenticate() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_self_updating_structs_wasm() { - test_self_updating_structs().await + test_self_updating_structs() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_recursive_self_updating_structs_wasm() { - test_recursive_self_updating_structs().await + test_recursive_self_updating_structs() } #[tokio::test] diff --git a/tests/guilds.rs b/tests/guilds.rs index 2bafbab..fd684dd 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -12,31 +12,31 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_creation_deletion_wasm() { - guild_creation_deletion().await + guild_creation_deletion() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn get_channels_wasm() { - get_channels().await + get_channels() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_create_ban_wasm() { - guild_create_ban().await + guild_create_ban() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn modify_guild_wasm() { - modify_guild().await + modify_guild() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_remove_member_wasm() { - guild_remove_member().await + guild_remove_member() } #[tokio::test] diff --git a/tests/instance.rs b/tests/instance.rs index 2466e60..1ffe914 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -8,7 +8,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_creation_deletion_wasm() { - guild_creation_deletion().await + guild_creation_deletion() } #[tokio::test] diff --git a/tests/invites.rs b/tests/invites.rs index a535b84..031afeb 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -9,7 +9,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn create_accept_invite_wasm() { - create_accept_invite().await + create_accept_invite() } #[tokio::test] diff --git a/tests/members.rs b/tests/members.rs index a2043ef..0dd3616 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -10,7 +10,7 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn add_remove_role_wasm() { - add_remove_role().await.unwrap() + add_remove_role().unwrap() } #[tokio::test] diff --git a/tests/messages.rs b/tests/messages.rs index fc46831..a39e9a0 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -13,25 +13,25 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn send_message_wasm() { - send_message().await + send_message() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn send_message_attachment_wasm() { - send_message_attachment().await + send_message_attachment() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn search_messages_wasm() { - search_messages().await + search_messages() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_stickies_wasm() { - test_stickies().await + test_stickies() } #[tokio::test] diff --git a/tests/relationships.rs b/tests/relationships.rs index 7f26e0f..112b47e 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -10,25 +10,25 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_relationships_wasm() { - test_get_relationships().await + test_get_relationships() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_mutual_relationships_wasm() { - test_get_mutual_relationships().await + test_get_mutual_relationships() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_relationship_friends_wasm() { - test_modify_relationship_friends().await + test_modify_relationship_friends() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_relationship_block_wasm() { - test_modify_relationship_block().await + test_modify_relationship_block() } #[tokio::test] diff --git a/tests/roles.rs b/tests/roles.rs index 0dd3b7f..5e85fbc 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -10,13 +10,13 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn create_and_get_roles_wasm() { - create_and_get_roles().await + create_and_get_roles() } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn get_and_delete_role_wasm() { - get_and_delete_role().await + get_and_delete_role() } #[tokio::test] From cfe40786396695e2600811f4ecbbee99fb3291f9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:15:23 +0100 Subject: [PATCH 108/130] Remove unnecessary code --- tests/auth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/auth.rs b/tests/auth.rs index b6731d7..c0ba90f 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -7,7 +7,6 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_registration() { let bundle = common::setup().await; From 37f3bcde940d732d3852967eddb2d811d1791ccf Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:22:00 +0100 Subject: [PATCH 109/130] Rename test function to generate_general_configuration_schema_wasm --- tests/instance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/instance.rs b/tests/instance.rs index 1ffe914..d8658bd 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -7,8 +7,8 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] -async fn guild_creation_deletion_wasm() { - guild_creation_deletion() +async fn generate_general_configuration_schema_wasm() { + generate_general_configuration_schema() } #[tokio::test] From 69f726af0e3fbdf47dd92b4814aa36cc907e588f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:36:00 +0100 Subject: [PATCH 110/130] Fix formatting in test functions --- tests/auth.rs | 2 +- tests/channels.rs | 12 ++++++------ tests/gateway.rs | 8 ++++---- tests/guilds.rs | 10 +++++----- tests/instance.rs | 2 +- tests/invites.rs | 2 +- tests/members.rs | 2 +- tests/messages.rs | 8 ++++---- tests/relationships.rs | 8 ++++---- tests/roles.rs | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/auth.rs b/tests/auth.rs index c0ba90f..921effa 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -23,5 +23,5 @@ async fn test_registration() { #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_registration_wasm() { - test_registration() + test_registration(); } diff --git a/tests/channels.rs b/tests/channels.rs index 270f64f..e67f3dd 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -13,32 +13,32 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_channel_wasm() { - get_channel() + get_channel(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_delete_channel_wasm() { - delete_channel() + delete_channel(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_channel_wasm() { - modify_channel() + modify_channel(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_channel_messages_wasm() { - get_channel_messages() + get_channel_messages(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_create_dm_wasm() { - create_dm() + create_dm(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_remove_add_person_from_to_dm_wasm() { - remove_add_person_from_to_dm() + remove_add_person_from_to_dm(); } #[tokio::test] diff --git a/tests/gateway.rs b/tests/gateway.rs index 54de74f..d0741f9 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -13,25 +13,25 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_gateway_establish_wasm() { - test_gateway_establish() + test_gateway_establish(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_gateway_authenticate_wasm() { - test_gateway_authenticate() + test_gateway_authenticate(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_self_updating_structs_wasm() { - test_self_updating_structs() + test_self_updating_structs(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_recursive_self_updating_structs_wasm() { - test_recursive_self_updating_structs() + test_recursive_self_updating_structs(); } #[tokio::test] diff --git a/tests/guilds.rs b/tests/guilds.rs index fd684dd..8537bb3 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -12,31 +12,31 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_creation_deletion_wasm() { - guild_creation_deletion() + guild_creation_deletion(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn get_channels_wasm() { - get_channels() + get_channels(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_create_ban_wasm() { - guild_create_ban() + guild_create_ban(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn modify_guild_wasm() { - modify_guild() + modify_guild(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn guild_remove_member_wasm() { - guild_remove_member() + guild_remove_member(); } #[tokio::test] diff --git a/tests/instance.rs b/tests/instance.rs index d8658bd..77ad366 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -8,7 +8,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn generate_general_configuration_schema_wasm() { - generate_general_configuration_schema() + generate_general_configuration_schema(); } #[tokio::test] diff --git a/tests/invites.rs b/tests/invites.rs index 031afeb..99d75b8 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -9,7 +9,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn create_accept_invite_wasm() { - create_accept_invite() + create_accept_invite(); } #[tokio::test] diff --git a/tests/members.rs b/tests/members.rs index 0dd3616..e7a34ed 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -10,7 +10,7 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn add_remove_role_wasm() { - add_remove_role().unwrap() + add_remove_role().unwrap(); } #[tokio::test] diff --git a/tests/messages.rs b/tests/messages.rs index a39e9a0..4355793 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -13,25 +13,25 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn send_message_wasm() { - send_message() + send_message(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn send_message_attachment_wasm() { - send_message_attachment() + send_message_attachment(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn search_messages_wasm() { - search_messages() + search_messages(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_stickies_wasm() { - test_stickies() + test_stickies(); } #[tokio::test] diff --git a/tests/relationships.rs b/tests/relationships.rs index 112b47e..e05e2fe 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -10,25 +10,25 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_relationships_wasm() { - test_get_relationships() + test_get_relationships(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_get_mutual_relationships_wasm() { - test_get_mutual_relationships() + test_get_mutual_relationships(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_relationship_friends_wasm() { - test_modify_relationship_friends() + test_modify_relationship_friends(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn test_modify_relationship_block_wasm() { - test_modify_relationship_block() + test_modify_relationship_block(); } #[tokio::test] diff --git a/tests/roles.rs b/tests/roles.rs index 5e85fbc..628e4fc 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -10,13 +10,13 @@ mod common; #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn create_and_get_roles_wasm() { - create_and_get_roles() + create_and_get_roles(); } #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] async fn get_and_delete_role_wasm() { - get_and_delete_role() + get_and_delete_role(); } #[tokio::test] From 3c419e831030c6c642d88ae962079c68997cfb1e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:47:13 +0100 Subject: [PATCH 111/130] Spawn local gateway task on wasm32 --- src/gateway/gateway.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index d10dbfb..55552b8 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -74,9 +74,14 @@ impl Gateway { }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + #[cfg(not(target_arch = "wasm32"))] task::spawn(async move { gateway.gateway_listen_task().await; }); + #[cfg(target_arch = "wasm32")] + task::spawn_local(async move { + gateway.gateway_listen_task().await; + }); Ok(GatewayHandle { url: websocket_url.clone(), From 8c2364b8d052b9ebdcbe0c91d570a9b94422a358 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 14:57:45 +0100 Subject: [PATCH 112/130] Refactor heartbeat task to support WebAssembly --- src/gateway/heartbeat.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index b8e4bec..25f4223 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -33,9 +33,14 @@ impl HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); + #[cfg(not(target_arch = "wasm32"))] let handle: JoinHandle<()> = task::spawn(async move { Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); + #[cfg(target_arch = "wasm32")] + let handle: JoinHandle<()> = task::spawn_local(move || { + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive); + }); Self { heartbeat_interval, From 509733bfb896b044150e53c722e2935830ca8162 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 15:14:28 +0100 Subject: [PATCH 113/130] Revert "Refactor heartbeat task to support WebAssembly" This reverts commit 8c2364b8d052b9ebdcbe0c91d570a9b94422a358. --- src/gateway/heartbeat.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index 25f4223..b8e4bec 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -33,14 +33,9 @@ impl HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); - #[cfg(not(target_arch = "wasm32"))] let handle: JoinHandle<()> = task::spawn(async move { Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); - #[cfg(target_arch = "wasm32")] - let handle: JoinHandle<()> = task::spawn_local(move || { - Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive); - }); Self { heartbeat_interval, From 5bd4b627c53dbb7ee5ef171212c423cefc3d8126 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 20 Nov 2023 15:14:34 +0100 Subject: [PATCH 114/130] Revert "Spawn local gateway task on wasm32" This reverts commit 3c419e831030c6c642d88ae962079c68997cfb1e. --- src/gateway/gateway.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 55552b8..d10dbfb 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -74,14 +74,9 @@ impl Gateway { }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello - #[cfg(not(target_arch = "wasm32"))] task::spawn(async move { gateway.gateway_listen_task().await; }); - #[cfg(target_arch = "wasm32")] - task::spawn_local(async move { - gateway.gateway_listen_task().await; - }); Ok(GatewayHandle { url: websocket_url.clone(), From e629334f3f760bf336aa6aa623c57546db15bbcb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 21 Nov 2023 17:10:19 +0100 Subject: [PATCH 115/130] Add FIXME comments for wasm compatibility --- src/gateway/gateway.rs | 1 + src/gateway/heartbeat.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index d10dbfb..7a3a81d 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -74,6 +74,7 @@ impl Gateway { }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + // FIXME: Doesn't work in WASM task::spawn(async move { gateway.gateway_listen_task().await; }); diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index b8e4bec..ea8f965 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -33,6 +33,7 @@ impl HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); + // FIXME: Doesn't work in WASM let handle: JoinHandle<()> = task::spawn(async move { Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); From 93c12fff63aa821ffe842e4598fbc6b8dc0de85a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:20:45 +0100 Subject: [PATCH 116/130] Add build instructions, especially for wasm --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index e654610..999db50 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,25 @@ We recommend checking out the examples directory, as well as the documentation f Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0. +## Development Setup + +Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version` +in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target. +You can do this by running `rustup target add wasm32-unknown-unknown`. + +### Testing + +In general, the tests will require you to run a local instance of the Spacebar server. You can find instructions on how +to do that [here](https://docs.spacebar.chat/setup/server/). You can find a pre-configured version of the server +[here](https://github.com/bitfl0wer/server). It is recommended to use the pre-configured version, as certain things +like "proxy connection checking" are already disabled on this version, which otherwise might break tests. + +### wasm + +To test for wasm, you will need to `cargo install wasm-pack`. You can then run +`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features` +to run the tests for wasm. + ## Versioning This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html). From f8979b7feb38a4030f2e94e04ed0ad987c31c8e9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:23:33 +0100 Subject: [PATCH 117/130] Remove 'handle', add wasm friendly task spawning --- src/gateway/heartbeat.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index ea8f965..1176517 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -4,7 +4,8 @@ use std::time::{self, Duration, Instant}; use tokio::sync::mpsc::{Receiver, Sender}; use safina_timer::sleep_until; -use tokio::task::{self, JoinHandle}; +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; use super::*; use crate::types; @@ -20,8 +21,6 @@ pub(super) struct HeartbeatHandler { pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, } impl HeartbeatHandler { @@ -33,15 +32,18 @@ impl HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); - // FIXME: Doesn't work in WASM - let handle: JoinHandle<()> = task::spawn(async move { + #[cfg(not(target_arch = "wasm32"))] + task::spawn(async move { + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); Self { heartbeat_interval, send, - handle, } } From 022bf9f320097f3ab332f85fb3670fbc41097ee0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:23:36 +0100 Subject: [PATCH 118/130] add wasm friendly task spawning --- src/gateway/gateway.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 7a3a81d..684b9d2 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -2,6 +2,7 @@ use std::time::Duration; use futures_util::{SinkExt, StreamExt}; use log::*; +#[cfg(not(target_arch = "wasm32"))] use tokio::task; use self::event::Events; @@ -74,10 +75,14 @@ impl Gateway { }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello - // FIXME: Doesn't work in WASM + #[cfg(not(target_arch = "wasm32"))] task::spawn(async move { gateway.gateway_listen_task().await; }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + gateway.gateway_listen_task().await; + }); Ok(GatewayHandle { url: websocket_url.clone(), From 8a588892b297c3fdac22974be6f5eb8e5cdc068d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:24:01 +0100 Subject: [PATCH 119/130] Add wasm-bindgen-futures --- Cargo.lock | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7c74e2a..fea0bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ dependencies = [ "tokio-tungstenite", "url", "wasm-bindgen", + "wasm-bindgen-futures", "wasm-bindgen-test", "ws_stream_wasm", ] diff --git a/Cargo.toml b/Cargo.toml index 24a7114..8ade54c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } ws_stream_wasm = "0.7.4" - +wasm-bindgen-futures = "0.4.38" [dev-dependencies] lazy_static = "1.4.0" From 0cae35e592f710aaf234ef8fc5e80f760a4b9c1c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:24:10 +0100 Subject: [PATCH 120/130] Remove broken test code --- tests/auth.rs | 6 ------ tests/channels.rs | 30 ------------------------------ tests/gateway.rs | 24 ------------------------ tests/guilds.rs | 30 ------------------------------ tests/instance.rs | 6 ------ tests/invites.rs | 6 ------ tests/members.rs | 6 ------ tests/messages.rs | 24 ------------------------ tests/relationships.rs | 24 ------------------------ tests/roles.rs | 12 ------------ 10 files changed, 168 deletions(-) diff --git a/tests/auth.rs b/tests/auth.rs index 921effa..f73a01f 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -19,9 +19,3 @@ async fn test_registration() { bundle.instance.clone().register_account(reg).await.unwrap(); common::teardown(bundle).await; } - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_registration_wasm() { - test_registration(); -} diff --git a/tests/channels.rs b/tests/channels.rs index e67f3dd..d489d94 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -10,36 +10,6 @@ mod common; use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_get_channel_wasm() { - get_channel(); -} -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_delete_channel_wasm() { - delete_channel(); -} -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_modify_channel_wasm() { - modify_channel(); -} -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_get_channel_messages_wasm() { - get_channel_messages(); -} -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_create_dm_wasm() { - create_dm(); -} -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_remove_add_person_from_to_dm_wasm() { - remove_add_person_from_to_dm(); -} #[tokio::test] async fn get_channel() { diff --git a/tests/gateway.rs b/tests/gateway.rs index d0741f9..2cbbe90 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -10,30 +10,6 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_gateway_establish_wasm() { - test_gateway_establish(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_gateway_authenticate_wasm() { - test_gateway_authenticate(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_self_updating_structs_wasm() { - test_self_updating_structs(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_recursive_self_updating_structs_wasm() { - test_recursive_self_updating_structs(); -} - #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { diff --git a/tests/guilds.rs b/tests/guilds.rs index 8537bb3..78d10c9 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -9,36 +9,6 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn guild_creation_deletion_wasm() { - guild_creation_deletion(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn get_channels_wasm() { - get_channels(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn guild_create_ban_wasm() { - guild_create_ban(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn modify_guild_wasm() { - modify_guild(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn guild_remove_member_wasm() { - guild_remove_member(); -} - #[tokio::test] async fn guild_creation_deletion() { let mut bundle = common::setup().await; diff --git a/tests/instance.rs b/tests/instance.rs index 77ad366..d7a2caa 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -5,12 +5,6 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn generate_general_configuration_schema_wasm() { - generate_general_configuration_schema(); -} - #[tokio::test] async fn generate_general_configuration_schema() { let bundle = common::setup().await; diff --git a/tests/invites.rs b/tests/invites.rs index 99d75b8..472b87a 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -6,12 +6,6 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn create_accept_invite_wasm() { - create_accept_invite(); -} - #[tokio::test] async fn create_accept_invite() { let mut bundle = common::setup().await; diff --git a/tests/members.rs b/tests/members.rs index e7a34ed..c95e6fc 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -7,12 +7,6 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn add_remove_role_wasm() { - add_remove_role().unwrap(); -} - #[tokio::test] async fn add_remove_role() -> ChorusResult<()> { let mut bundle = common::setup().await; diff --git a/tests/messages.rs b/tests/messages.rs index 4355793..a06a615 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -10,30 +10,6 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn send_message_wasm() { - send_message(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn send_message_attachment_wasm() { - send_message_attachment(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn search_messages_wasm() { - search_messages(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_stickies_wasm() { - test_stickies(); -} - #[tokio::test] async fn send_message() { let mut bundle = common::setup().await; diff --git a/tests/relationships.rs b/tests/relationships.rs index e05e2fe..d013223 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -7,30 +7,6 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_get_relationships_wasm() { - test_get_relationships(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_get_mutual_relationships_wasm() { - test_get_mutual_relationships(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_modify_relationship_friends_wasm() { - test_modify_relationship_friends(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn test_modify_relationship_block_wasm() { - test_modify_relationship_block(); -} - #[tokio::test] async fn test_get_mutual_relationships() { let mut bundle = common::setup().await; diff --git a/tests/roles.rs b/tests/roles.rs index 628e4fc..547db47 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -7,18 +7,6 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn create_and_get_roles_wasm() { - create_and_get_roles(); -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen_test] -async fn get_and_delete_role_wasm() { - get_and_delete_role(); -} - #[tokio::test] async fn create_and_get_roles() { let mut bundle = common::setup().await; From 38c95255c58579fbf5584458ad56211b7c76f2fb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:39:32 +0100 Subject: [PATCH 121/130] Replace #[tokio::test] w/ wasm-ok macro calls --- tests/auth.rs | 3 ++- tests/channels.rs | 15 ++++++++++----- tests/gateway.rs | 12 ++++++++---- tests/guilds.rs | 15 ++++++++++----- tests/instance.rs | 3 ++- tests/invites.rs | 3 ++- tests/members.rs | 3 ++- tests/messages.rs | 12 ++++++++---- tests/relationships.rs | 12 ++++++++---- tests/roles.rs | 6 ++++-- 10 files changed, 56 insertions(+), 28 deletions(-) diff --git a/tests/auth.rs b/tests/auth.rs index f73a01f..086c8ba 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -7,7 +7,8 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_registration() { let bundle = common::setup().await; let reg = RegisterSchema { diff --git a/tests/channels.rs b/tests/channels.rs index d489d94..864165a 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -11,7 +11,8 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channel() { let mut bundle = common::setup().await; let bundle_channel = bundle.channel.read().unwrap().clone(); @@ -24,7 +25,8 @@ async fn get_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn delete_channel() { let mut bundle = common::setup().await; let channel_guard = bundle.channel.write().unwrap().clone(); @@ -33,7 +35,8 @@ async fn delete_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn modify_channel() { const CHANNEL_NAME: &str = "beepboop"; let mut bundle = common::setup().await; @@ -91,7 +94,8 @@ async fn modify_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channel_messages() { let mut bundle = common::setup().await; let channel_id: Snowflake = bundle.channel.read().unwrap().id; @@ -147,7 +151,8 @@ async fn get_channel_messages() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_dm() { let mut bundle = common::setup().await; let other_user = bundle.create_user("integrationtestuser2").await; diff --git a/tests/gateway.rs b/tests/gateway.rs index 2cbbe90..b564cde 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -10,7 +10,8 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { let bundle = common::setup().await; @@ -19,7 +20,8 @@ async fn test_gateway_establish() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection and authenticating async fn test_gateway_authenticate() { let bundle = common::setup().await; @@ -33,7 +35,8 @@ async fn test_gateway_authenticate() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { let mut bundle = common::setup().await; let received_channel = bundle @@ -66,7 +69,8 @@ async fn test_self_updating_structs() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_recursive_self_updating_structs() { // Setup let mut bundle = common::setup().await; diff --git a/tests/guilds.rs b/tests/guilds.rs index 78d10c9..360113d 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -9,7 +9,8 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_creation_deletion() { let mut bundle = common::setup().await; @@ -31,7 +32,8 @@ async fn guild_creation_deletion() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channels() { let mut bundle = common::setup().await; let guild = bundle.guild.read().unwrap().clone(); @@ -39,7 +41,8 @@ async fn get_channels() { common::teardown(bundle).await; } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_create_ban() { // TODO: When routes exist to check if user x is on guild y, add this as an assertion to check // if Spacebar actually bans the user. @@ -76,7 +79,8 @@ async fn guild_create_ban() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn modify_guild() { let mut bundle = common::setup().await; let schema = GuildModifySchema { @@ -91,7 +95,8 @@ async fn modify_guild() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_remove_member() { let mut bundle = common::setup().await; let channel = bundle.channel.read().unwrap().clone(); diff --git a/tests/instance.rs b/tests/instance.rs index d7a2caa..56f4d6d 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -5,7 +5,8 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn generate_general_configuration_schema() { let bundle = common::setup().await; bundle diff --git a/tests/invites.rs b/tests/invites.rs index 472b87a..d830ee8 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -6,7 +6,8 @@ use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_accept_invite() { let mut bundle = common::setup().await; let channel = bundle.channel.read().unwrap().clone(); diff --git a/tests/members.rs b/tests/members.rs index c95e6fc..c9072ef 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -7,7 +7,8 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn add_remove_role() -> ChorusResult<()> { let mut bundle = common::setup().await; let guild = bundle.guild.read().unwrap().id; diff --git a/tests/messages.rs b/tests/messages.rs index a06a615..8228aa7 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -10,7 +10,8 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn send_message() { let mut bundle = common::setup().await; let message = types::MessageSendSchema { @@ -22,7 +23,8 @@ async fn send_message() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn send_message_attachment() { let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); @@ -59,7 +61,8 @@ async fn send_message_attachment() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn search_messages() { let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); @@ -105,7 +108,8 @@ async fn search_messages() { assert_eq!(query_result.get(0).unwrap().id, message.id); } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_stickies() { let mut bundle = common::setup().await; let message = types::MessageSendSchema { diff --git a/tests/relationships.rs b/tests/relationships.rs index d013223..156f6eb 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -7,7 +7,8 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_get_mutual_relationships() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -28,7 +29,8 @@ async fn test_get_mutual_relationships() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_get_relationships() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -51,7 +53,8 @@ async fn test_get_relationships() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_modify_relationship_friends() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -102,7 +105,8 @@ async fn test_modify_relationship_friends() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_modify_relationship_block() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; diff --git a/tests/roles.rs b/tests/roles.rs index 547db47..ca58582 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -7,7 +7,8 @@ wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_and_get_roles() { let mut bundle = common::setup().await; let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS; @@ -36,7 +37,8 @@ async fn create_and_get_roles() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_and_delete_role() { let mut bundle = common::setup().await; let guild_id = bundle.guild.read().unwrap().id; From bdb505218c37613d43f0550fa48fed4df83bca64 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 14:52:49 +0100 Subject: [PATCH 122/130] Add fixme note about tests requiring std::fs --- tests/messages.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/messages.rs b/tests/messages.rs index 8228aa7..fc59a42 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -26,6 +26,9 @@ async fn send_message() { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn send_message_attachment() { + /// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files. + #[cfg(target_arch = "wasm32")] + return; let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); let mut buffer = Vec::new(); @@ -64,6 +67,9 @@ async fn send_message_attachment() { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn search_messages() { + /// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files. + #[cfg(target_arch = "wasm32")] + return; let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); let mut buffer = Vec::new(); From 4e182c143033ce3982e0c2839d98dbb2acba8c87 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 15:01:51 +0100 Subject: [PATCH 123/130] Update supported platforms in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 999db50..e435086 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ dbg!(&user.object.read().unwrap().username); ## Supported Platforms All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported. -We are currently working on adding full support for `wasm32-unknown-unknown`. This will allow you to use Chorus in +`wasm32-unknown-unknown` is a supported compilation target on versions `>0.11.0`. This allows you to use Chorus in your browser, or in any other environment that supports WebAssembly. We recommend checking out the examples directory, as well as the documentation for more information. From 56e02960eaf7be13119d017bbd4fbf8d56cd3097 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 15:13:26 +0100 Subject: [PATCH 124/130] Split up wasm-tests into 3 seperate tests --- .github/workflows/build_and_test.yml | 56 +++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c0e314f..cefd260 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -45,7 +45,7 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - macos: + wasm-safari: runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -73,5 +73,59 @@ jobs: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-gecko: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-chrome: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file From ace9b57d695a5c618c941b447a7b7f91ef88a035 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 16:14:33 +0100 Subject: [PATCH 125/130] Update supported compilation target for Chorus to versions 0.12.0 and up --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e435086..89d849f 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ dbg!(&user.object.read().unwrap().username); ## Supported Platforms All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported. -`wasm32-unknown-unknown` is a supported compilation target on versions `>0.11.0`. This allows you to use Chorus in -your browser, or in any other environment that supports WebAssembly. +`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use +Chorus in your browser, or in any other environment that supports WebAssembly. We recommend checking out the examples directory, as well as the documentation for more information. From e828c3cadaac8b3083d1f2ce7a5a2c50a48be332 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 16:14:47 +0100 Subject: [PATCH 126/130] Bump version, explain why custom `reqwest` --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fea0bf4..2c7b5d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chorus" -version = "0.11.0" +version = "0.12.0" dependencies = [ "async-trait", "base64 0.21.5", diff --git a/Cargo.toml b/Cargo.toml index 8ade54c..4387273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "chorus" description = "A library for interacting with multiple Spacebar-compatible Instances at once." -version = "0.11.0" +version = "0.12.0" license = "AGPL-3.0" edition = "2021" repository = "https://github.com/polyphony-chat/chorus" @@ -27,7 +27,7 @@ serde_repr = "0.1.16" reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [ "multipart", "json", -] } +] } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22 url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.4" From c5e6c0b8004e03e712554e08c14ed95e06427a31 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 16:34:43 +0100 Subject: [PATCH 127/130] Add tests for GatewayMessage to increase coverage --- tests/gateway.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/gateway.rs b/tests/gateway.rs index b564cde..68a203a 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,6 +2,7 @@ mod common; use std::sync::{Arc, RwLock}; +use chorus::errors::GatewayError; use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; // PRETTYFYME: Move common wasm setup to common.rs @@ -125,3 +126,92 @@ async fn test_recursive_self_updating_structs() { assert_eq!(guild_role_inner.name, "yippieee".to_string()); common::teardown(bundle).await; } + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn test_error() { + let error = GatewayMessage("4000".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Unknown); + let error = GatewayMessage("4001".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::UnknownOpcode); + let error = GatewayMessage("4002".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Decode); + let error = GatewayMessage("4003".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::NotAuthenticated); + let error = GatewayMessage("4004".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::AuthenticationFailed); + let error = GatewayMessage("4005".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::AlreadyAuthenticated); + let error = GatewayMessage("4007".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidSequenceNumber); + let error = GatewayMessage("4008".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::RateLimited); + let error = GatewayMessage("4009".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::SessionTimedOut); + let error = GatewayMessage("4010".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidShard); + let error = GatewayMessage("4011".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::ShardingRequired); + let error = GatewayMessage("4012".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidAPIVersion); + let error = GatewayMessage("4013".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); + let error = GatewayMessage("4014".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::DisallowedIntents); +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn test_error_message() { + let error = GatewayMessage("Unknown Error".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Unknown); + let error = GatewayMessage("Unknown Opcode".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::UnknownOpcode); + let error = GatewayMessage("Decode Error".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Decode); + let error = GatewayMessage("Not Authenticated".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::NotAuthenticated); + let error = GatewayMessage("Authentication Failed".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::AuthenticationFailed); + let error = GatewayMessage("Already Authenticated".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::AlreadyAuthenticated); + let error = GatewayMessage("Invalid Seq".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidSequenceNumber); + let error = GatewayMessage("Rate Limited".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::RateLimited); + let error = GatewayMessage("Session Timed Out".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::SessionTimedOut); + let error = GatewayMessage("Invalid Shard".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidShard); + let error = GatewayMessage("Sharding Required".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::ShardingRequired); + let error = GatewayMessage("Invalid API Version".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidAPIVersion); + let error = GatewayMessage("Invalid Intent(s)".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); + let error = GatewayMessage("Disallowed Intent(s)".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::DisallowedIntents); + // Also test the dot thing + let error = GatewayMessage("Invalid Intent(s).".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); +} From f973c559528231f309a8c21f9e486703f83c8b71 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 16:43:03 +0100 Subject: [PATCH 128/130] Prevent CI from running twice on PR to main --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index cefd260..a6abe43 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -2,7 +2,7 @@ name: Build and Test on: push: - branches: [ "main", "dev" ] + branches: [ "main" ] pull_request: branches: [ "main", "dev" ] From 84f8fc0fa402abe79d4e1fcf34cdbcce67f7ff78 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 16:43:48 +0100 Subject: [PATCH 129/130] Update branch filter for push event --- .github/workflows/rust-clippy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 15dedd4..4c45b0b 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -11,7 +11,7 @@ name: rust-clippy analyze on: push: - branches: [ "main", "preserve/*", "dev" ] + branches: [ "main", "preserve/*" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main", "dev" ] From b03b703c22ab9c71a56cd6e4c7bf183d03807a9f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 17:32:22 +0100 Subject: [PATCH 130/130] Update reqwest dependency to version 0.11.22 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4387273..1d76722 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ serde_repr = "0.1.16" reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [ "multipart", "json", -] } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22 +], version = "0.11.22" } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22 url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.4"