From b0b972966295e8e1273bd255cd5a5c94cc384f4b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 12:43:00 +0100 Subject: [PATCH 01/73] + 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 15b5eab08caf8933fb570b1e42b8b82c57928bec Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 12:57:17 +0100 Subject: [PATCH 02/73] 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 6ad5a5851f39e021d513db43a69d734900379ec1 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 13:55:55 +0100 Subject: [PATCH 03/73] 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 7f48eedb2cf95edd62e04347458920691604c8f3 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:18:22 +0100 Subject: [PATCH 04/73] 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 ba3acd8d08e1685170be1b070bd94bcaa09e2417 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:26:46 +0100 Subject: [PATCH 05/73] 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 5fff060f4b9231229b853f9e07b0d43b6589b824 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:29:37 +0100 Subject: [PATCH 06/73] 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 3124c452d86b845a127c54afa0502211d877143c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 15:38:20 +0100 Subject: [PATCH 07/73] 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 cafd645dd3fd809aa0ddd8f3a24d4fed3a2a8613 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 17:34:16 +0100 Subject: [PATCH 08/73] 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 14505464297f9be3bc395bdc2218a4ce575307b4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 13 Nov 2023 19:20:38 +0100 Subject: [PATCH 09/73] 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 4106758e0ef02bb4e62b82106abb2448c180c473 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 16:11:00 +0100 Subject: [PATCH 10/73] bump version --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb7696..576f2ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,7 +177,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chorus" -version = "0.10.1" +version = "0.11.0" dependencies = [ "async-trait", "base64 0.21.3", @@ -2338,9 +2338,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", From 0c0252acd21b1ececddd903f6497e46994e9ef47 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 16:42:01 +0100 Subject: [PATCH 11/73] 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 7ecf44386e311cb29a08b0242a020d710d1c163f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 19:00:43 +0100 Subject: [PATCH 12/73] 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 80a0ab9fb2bec91a97bf3af05ad17737ce9d7c2c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:04:23 +0100 Subject: [PATCH 13/73] 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 8888b2f060f7cd1490dde42751b47a888b80b35a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:05:21 +0100 Subject: [PATCH 14/73] 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 82768c677277c048cfb2e7a261c9b81358d1ee3e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:05:47 +0100 Subject: [PATCH 15/73] 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 2f3927d8c45866f3def3a56d841d0b5bfeff4c70 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 20:46:46 +0100 Subject: [PATCH 16/73] 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 855518c47f205c705e7480422dc5d916964e9e06 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 14 Nov 2023 21:14:05 +0100 Subject: [PATCH 17/73] 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 9258e0d93fbe078ca55398bee97315cd58b42934 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 00:04:04 +0100 Subject: [PATCH 18/73] 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 da40d7a4e2f7dda9043fba169236878338e06e56 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 18:44:19 +0100 Subject: [PATCH 19/73] 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 0499dac983d92cebb2657c3fe0bace191cb0f7d9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:18:50 +0100 Subject: [PATCH 20/73] 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 c6b3a28132863d665a296aaaeb0522ed23635e11 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:26:47 +0100 Subject: [PATCH 21/73] 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 82dd3d22b373fea31c20d70dd6cbcc7bc667a0a2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:56:58 +0100 Subject: [PATCH 22/73] 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 497c3bda74492e71f62f23fba2fde7e8f5cd839a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 20:58:10 +0100 Subject: [PATCH 23/73] 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 b738f4d5433156cede7c45c1689d0090e7d55467 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 21:03:43 +0100 Subject: [PATCH 24/73] 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 0c6b120edf923aaa6b95d37dbc772c05de1f69b5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 21:09:24 +0100 Subject: [PATCH 25/73] 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 47d09fe8229c9205ece18730870e1adc52fb7e27 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:26:15 +0100 Subject: [PATCH 26/73] 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 7f239d3ed3d49798f00ad9f5df379d476b9515a3 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:31:44 +0100 Subject: [PATCH 27/73] 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 c50762e92daa9293ed597df436ea41a19f8db13f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:32:13 +0100 Subject: [PATCH 28/73] 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 bacc1bff6939fd6e251a2ae99407472bfc629186 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 15 Nov 2023 22:32:31 +0100 Subject: [PATCH 29/73] 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 79b437cd338c63b099f0a78624f136f72a084b02 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:23:44 +0100 Subject: [PATCH 30/73] 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 b9281b2300b20f5ad5dc96986d444d48766193d5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:24:02 +0100 Subject: [PATCH 31/73] 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 5ffd42c1bfe11d56fe397020b13c72f9dc943546 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:24:25 +0100 Subject: [PATCH 32/73] 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 cda9bd35b0919d1769eb79737da43ea2b53c4be8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 16:28:55 +0100 Subject: [PATCH 33/73] 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 7eb5325a4148ae39d9a2167e9394213c280523fb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 17:47:40 +0100 Subject: [PATCH 34/73] 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 6c2d67213ad11ef796ec20b1fa89bc1b4b1830bd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:20:11 +0100 Subject: [PATCH 35/73] 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 1f94f294bc2e31be7b83e0c42ad8a2a347229298 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:20:45 +0100 Subject: [PATCH 36/73] 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 033f2fa4b79dd184755967ffe9d6d875bfe949d2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 18:30:09 +0100 Subject: [PATCH 37/73] 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 da944f729c82202a6b0cc73a1c5858ecfe0f6151 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:05:36 +0100 Subject: [PATCH 38/73] Revert "Make GatewayMessage use new Adapter Type" This reverts commit 033f2fa4b79dd184755967ffe9d6d875bfe949d2. --- 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 36f839b1c03d050ff112070dc8f738caabe2000c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:07:14 +0100 Subject: [PATCH 39/73] Revert "Add Adapter GatewayMessageData" This reverts commit 1f94f294bc2e31be7b83e0c42ad8a2a347229298. --- 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 0a8105d7fc9ced683f14569e26cc9f45da1b4560 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 19:25:55 +0100 Subject: [PATCH 40/73] 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 595832ef63fd2d5f75114b80871c308675825d8c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 16 Nov 2023 20:02:01 +0100 Subject: [PATCH 41/73] 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 6e43b1f344ab4f397dd18eb210335eb846f20cda Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 12:27:50 +0100 Subject: [PATCH 42/73] 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 43a692b37a2d51e086eef35fd1860b64af74c801 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 15:27:22 +0100 Subject: [PATCH 43/73] 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 e20da509bfdc101037995103a9648e428b1f7a06 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:08:12 +0100 Subject: [PATCH 44/73] 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 33d8adc2f84a17599461e449aa7f3599b6faf61e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:39:01 +0100 Subject: [PATCH 45/73] 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 6b4084084f37699d6a2bce587520204d7fbb7b98 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:41:11 +0100 Subject: [PATCH 46/73] 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 70b6f3f6d142bbc3a9c0ecb34cd52ac65b1288fa Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 18:50:47 +0100 Subject: [PATCH 47/73] 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 08e7bc53c2a54792f3a9e67b6714138076076837 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:07:56 +0100 Subject: [PATCH 48/73] 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 b03d6b45a4fab158673752864fc5ff0f61eb2ab7 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:13:26 +0100 Subject: [PATCH 49/73] 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 fc6a45b4fcd1c3fb7560b9e54a641a2d1b434b03 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 19:18:55 +0100 Subject: [PATCH 50/73] 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 92113f3f3eddfa32b53fc5dc254aaff9a6f83ce0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:28 +0100 Subject: [PATCH 51/73] 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 cf12923e996caacffebfdc5b8290786f596be600 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:34 +0100 Subject: [PATCH 52/73] 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 1fa1c173a4a097777e12c29d7b74609d7afafaa0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:39 +0100 Subject: [PATCH 53/73] 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 f5b9973f0c6557838761f3298af0166092ae3dab Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:45 +0100 Subject: [PATCH 54/73] 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 ad7626147d2498f2970b0ca484a0d82d3dd8096c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 20:02:50 +0100 Subject: [PATCH 55/73] 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 5785d73eea23fdfb088adcebb6bc82b37d680f55 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 18 Nov 2023 21:42:48 +0100 Subject: [PATCH 56/73] 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 9d88f50bf74bf252a3b5a05639710e0092dba640 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 01:31:04 +0100 Subject: [PATCH 57/73] 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 c87ad0ea6fc2f926802aa2f2a5388a54d3e3c0e8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:51:07 +0100 Subject: [PATCH 58/73] 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 f2f212ac742c9c19428313343712d6f617ccb6ff Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:51:13 +0100 Subject: [PATCH 59/73] 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 129e72dbf0e43aaf5b29efa490cc1d83bb147daf Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 12:52:11 +0100 Subject: [PATCH 60/73] 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 4dc8dd94339368cffed627d5f0e61f848726102c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:42:05 +0100 Subject: [PATCH 61/73] 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 f7ede92e360547430ba1195fd25e1f482a4415f4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:42:48 +0100 Subject: [PATCH 62/73] 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 3598e2b5e735112a1eb6a784faff3d07222e6142 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:43:21 +0100 Subject: [PATCH 63/73] 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 848bfcd13b0c4d03442c5cb4e7bf1ad78e0f81c6 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 16:44:38 +0100 Subject: [PATCH 64/73] 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 064014474c89cf2e59e6238e1080f31a8b81bbc4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 17:07:08 +0100 Subject: [PATCH 65/73] 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 9a998e57fac148a3b9fff99d57fda9be8c983fce Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:08:53 +0100 Subject: [PATCH 66/73] 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 7d9c8393bfb263e9dead57ae1c77c66bd24210d9 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:13:52 +0100 Subject: [PATCH 67/73] 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 b37f487b7df2c21e6c4e5f4c0cf0f48e1ed606d0 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:15:00 +0100 Subject: [PATCH 68/73] 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 487bcd5809af64db697f56e8aebd137f072c7c31 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:18:08 +0100 Subject: [PATCH 69/73] 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 d7ec422e2b5ec762224324dd2a8af129139486f8 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 19 Nov 2023 17:46:49 +0100 Subject: [PATCH 70/73] 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 5a9a0d152cdbe1766c040fff5ebcf28437ee242a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 18:27:49 +0100 Subject: [PATCH 71/73] 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 0f2d2c8136d01774f482e0fa5f5dc92ff47890bb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 19:12:29 +0100 Subject: [PATCH 72/73] 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 50b86d4472715e133a1a6edc04a47380b70dcb43 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 19 Nov 2023 19:18:25 +0100 Subject: [PATCH 73/73] 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;