diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ec2f3b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 31962c2..a6abe43 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -2,7 +2,7 @@ name: Build and Test on: push: - branches: [ "main", "dev" ] + branches: [ "main" ] pull_request: branches: [ "main", "dev" ] @@ -10,7 +10,7 @@ env: CARGO_TERM_COLOR: always jobs: - rust: + linux: runs-on: ubuntu-latest @@ -33,6 +33,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + prefix-key: "linux" - name: Build, Test and Publish Coverage run: | if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then @@ -44,4 +45,87 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - + wasm-safari: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-gecko: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-chrome: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 15dedd4..4c45b0b 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -11,7 +11,7 @@ name: rust-clippy analyze on: push: - branches: [ "main", "preserve/*", "dev" ] + branches: [ "main", "preserve/*" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main", "dev" ] diff --git a/Cargo.lock b/Cargo.lock index 2834823..9e5c578 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,17 @@ dependencies = [ "syn 2.0.39", ] +[[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" @@ -178,7 +189,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chorus" -version = "0.11.0" +version = "0.12.0" dependencies = [ "async-trait", "base64 0.21.5", @@ -188,18 +199,20 @@ dependencies = [ "custom_error", "discortp", "futures-util", + "getrandom", "hostname", "http", "jsonwebtoken", "lazy_static", "log", + "native-tls", "poem", "rand", "regex", "reqwest", "rustls", "rustls-native-certs", - "rusty-hook", + "safina-timer", "serde", "serde-aux", "serde_json", @@ -210,6 +223,10 @@ dependencies = [ "tokio", "tokio-tungstenite", "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "ws_stream_wasm", ] [[package]] @@ -239,12 +256,13 @@ dependencies = [ ] [[package]] -name = "ci_info" -version = "0.10.2" +name = "console_error_panic_hook" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "envmnt", + "cfg-if", + "wasm-bindgen", ] [[package]] @@ -365,9 +383,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "der" @@ -436,16 +454,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" @@ -454,9 +462,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys", @@ -533,10 +541,19 @@ dependencies = [ ] [[package]] -name = "fsio" -version = "0.1.3" +name = "futures" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] [[package]] name = "futures-channel" @@ -611,6 +628,7 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -632,15 +650,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" @@ -648,8 +657,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -660,9 +671,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -670,7 +681,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -787,9 +798,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", @@ -1107,10 +1118,15 @@ dependencies = [ ] [[package]] -name = "nias" -version = "0.5.0" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] [[package]] name = "no-std-net" @@ -1309,6 +1325,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-lite" version = "0.2.13" @@ -1380,9 +1406,9 @@ dependencies = [ [[package]] name = "poem" -version = "1.3.58" +version = "1.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc7ae19f3e791ae8108b08801abb3708d64d3a16490c720e0b81040cae87b5d" +checksum = "504774c97b0744c1ee108a37e5a65a9745a4725c4c06277521dabc28eb53a904" dependencies = [ "async-trait", "bytes", @@ -1391,6 +1417,7 @@ dependencies = [ "http", "hyper", "mime", + "nix", "parking_lot", "percent-encoding", "pin-project-lite", @@ -1405,13 +1432,14 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "wildmatch", ] [[package]] name = "poem-derive" -version = "1.3.58" +version = "1.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2550a0bce7273b278894ef3ccc5a6869e7031b6870042f3cc6826ed9faa980a6" +checksum = "42ddcf4680d8d867e1e375116203846acb088483fa2070244f90589f458bbb31" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1433,11 +1461,10 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "once_cell", "toml_edit", ] @@ -1530,8 +1557,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#858197c528f074f00397f7e2675d3eb15cc10a75" dependencies = [ "base64 0.21.5", "bytes", @@ -1606,9 +1632,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f" dependencies = [ "const-oid", "digest", @@ -1631,10 +1657,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustix" -version = "0.38.22" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80109a168d9bc0c7f483083244543a6eb0dba02295d33ca268145e6190d6df0c" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -1645,9 +1680,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring 0.17.5", @@ -1669,9 +1704,9 @@ 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.5", ] @@ -1686,24 +1721,21 @@ 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" 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" @@ -1713,6 +1745,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1721,12 +1759,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]] @@ -1753,10 +1791,22 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.192" +name = "semver" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +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.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1774,9 +1824,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -1868,20 +1918,11 @@ 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" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -2326,9 +2367,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys", @@ -2405,15 +2444,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" @@ -2422,9 +2452,9 @@ checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.1.0", "toml_datetime", @@ -2546,12 +2576,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" @@ -2680,6 +2704,31 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "web-sys" version = "0.3.65" @@ -2696,6 +2745,12 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +[[package]] +name = "wildmatch" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86" + [[package]] name = "winapi" version = "0.3.9" @@ -2813,19 +2868,38 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.7.25" +name = "ws_stream_wasm" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +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 = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" dependencies = [ "proc-macro2", "quote", @@ -2834,6 +2908,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index ecdb3ab..da7c127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "chorus" description = "A library for interacting with multiple Spacebar-compatible Instances at once." -version = "0.11.0" +version = "0.12.0" license = "AGPL-3.0" edition = "2021" repository = "https://github.com/polyphony-chat/chorus" @@ -11,33 +11,38 @@ website = ["https://discord.com/invite/m3FpcapGDD"] [features] -default = ["client"] -backend = ["poem", "sqlx"] -client = ["discortp"] +default = ["client", "rt-multi-thread"] +backend = ["dep:poem", "dep:sqlx"] +rt-multi-thread = ["tokio/rt-multi-thread"] +rt = ["tokio/rt"] +client = ["voice"] +voice = ["dep:discortp"] [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" 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", +], version = "0.11.22" } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22 url = "2.4.0" chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.4" custom_error = "1.9.2" -tokio-tungstenite = { version = "0.20.1", 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 } +jsonwebtoken = "8.3.0" +log = "0.4.20" +async-trait = "0.1.73" +chorus-macros = "0.2.0" sqlx = { version = "0.7.1", features = [ "mysql", "sqlite", @@ -48,16 +53,28 @@ sqlx = { version = "0.7.1", features = [ "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" discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] } rustls = "0.21.8" rustls-native-certs = "0.6.3" +safina-timer = "0.1.11" rand = "0.8.5" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +rustls = "0.21.8" +rustls-native-certs = "0.6.3" +tokio-tungstenite = { version = "0.20.1", features = [ + "rustls-tls-native-roots", + "rustls-native-certs", +] } +native-tls = "0.2.11" +hostname = "0.3.1" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2.11", features = ["js"] } +ws_stream_wasm = "0.7.4" +wasm-bindgen-futures = "0.4.38" + [dev-dependencies] -tokio = { version = "1.32.0", features = ["full"] } lazy_static = "1.4.0" -rusty-hook = "0.11.2" +wasm-bindgen-test = "0.3.38" +wasm-bindgen = "0.2.88" diff --git a/README.md b/README.md index e654610..89d849f 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ dbg!(&user.object.read().unwrap().username); ## Supported Platforms All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported. -We are currently working on adding full support for `wasm32-unknown-unknown`. This will allow you to use Chorus in -your browser, or in any other environment that supports WebAssembly. +`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use +Chorus in your browser, or in any other environment that supports WebAssembly. We recommend checking out the examples directory, as well as the documentation for more information. @@ -107,6 +107,25 @@ We recommend checking out the examples directory, as well as the documentation f Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0. +## Development Setup + +Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version` +in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target. +You can do this by running `rustup target add wasm32-unknown-unknown`. + +### Testing + +In general, the tests will require you to run a local instance of the Spacebar server. You can find instructions on how +to do that [here](https://docs.spacebar.chat/setup/server/). You can find a pre-configured version of the server +[here](https://github.com/bitfl0wer/server). It is recommended to use the pre-configured version, as certain things +like "proxy connection checking" are already disabled on this version, which otherwise might break tests. + +### wasm + +To test for wasm, you will need to `cargo install wasm-pack`. You can then run +`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features` +to run the tests for wasm. + ## Versioning This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html). diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 1572aa9..a13c935 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,11 +1,12 @@ use async_trait::async_trait; +use chorus::gateway::Gateway; use chorus::{ self, - gateway::{Gateway, Observer}, + gateway::Observer, types::{GatewayIdentifyPayload, GatewayReady}, }; use std::{sync::Arc, time::Duration}; -use tokio::{self, time::sleep}; +use tokio::{self}; // This example creates a simple gateway connection and a basic observer struct @@ -24,13 +25,13 @@ 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(); // Initiate the gateway connection - let gateway = Gateway::new(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; @@ -53,9 +54,10 @@ async fn main() { let mut identify = GatewayIdentifyPayload::common(); identify.token = token; gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 26e8416..2996283 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,16 +1,16 @@ use std::time::Duration; -use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; -use tokio::time::sleep; +use chorus::gateway::Gateway; +use chorus::{self, types::GatewayIdentifyPayload}; /// 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(); // 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::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 @@ -26,10 +26,10 @@ async fn main() { identify.token = token; // Send off the event - gateway.send_identify(identify).await; + safina_timer::start_timer_thread(); // Do something on the main thread so we don't quit loop { - sleep(Duration::MAX).await; + safina_timer::sleep_for(Duration::MAX).await } } diff --git a/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/api/auth/login.rs b/src/api/auth/login.rs index 272ee04..1d9fc8a 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -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::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 3ad4a60..ae3b219 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -3,9 +3,9 @@ use std::sync::{Arc, RwLock}; pub use login::*; pub use register::*; +use crate::gateway::Gateway; use crate::{ errors::ChorusResult, - gateway::Gateway, instance::{ChorusUser, Instance}, types::{GatewayIdentifyPayload, User}, }; @@ -25,7 +25,7 @@ impl Instance { .await .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::new(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 d10915e..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::Gateway; +use crate::gateway::{Gateway, 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::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/backends/mod.rs b/src/gateway/backends/mod.rs new file mode 100644 index 0000000..edb5dc9 --- /dev/null +++ b/src/gateway/backends/mod.rs @@ -0,0 +1,23 @@ +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub mod tungstenite; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub use tungstenite::*; + +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub mod wasm; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub use wasm::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type Sink = tungstenite::TungsteniteSink; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type Stream = tungstenite::TungsteniteStream; +#[cfg(all(not(target_arch = "wasm32"), feature = "client"))] +pub type WebSocketBackend = tungstenite::TungsteniteBackend; + +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Sink = wasm::WasmSink; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type Stream = wasm::WasmStream; +#[cfg(all(target_arch = "wasm32", feature = "client"))] +pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/gateway/backends/tungstenite.rs b/src/gateway/backends/tungstenite.rs new file mode 100644 index 0000000..5184329 --- /dev/null +++ b/src/gateway/backends/tungstenite.rs @@ -0,0 +1,66 @@ +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, +}; + +use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; + +#[derive(Debug, Clone)] +pub struct TungsteniteBackend; + +// These could be made into inherent associated types when that's stabilized +pub type TungsteniteSink = + SplitSink>, tungstenite::Message>; +pub type TungsteniteStream = SplitStream>>; + +impl TungsteniteBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { + let mut roots = rustls::RootCertStore::empty(); + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") + { + 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/backends/wasm.rs b/src/gateway/backends/wasm.rs new file mode 100644 index 0000000..e9927ac --- /dev/null +++ b/src/gateway/backends/wasm.rs @@ -0,0 +1,50 @@ +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; + +use ws_stream_wasm::*; + +use crate::errors::GatewayError; +use crate::gateway::GatewayMessage; + +#[derive(Debug, Clone)] +pub struct WasmBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WasmSink = SplitSink; +pub type WasmStream = SplitStream; + +impl WasmBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { + let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { + Ok(stream) => Ok(stream), + Err(e) => Err(GatewayError::CannotConnect { + error: e.to_string(), + }), + }?; + + Ok(websocket_stream.split()) + } +} + +impl From for WsMessage { + fn from(message: GatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for GatewayMessage { + fn from(value: WsMessage) -> Self { + match value { + WsMessage::Text(text) => Self(text), + WsMessage::Binary(bin) => { + let mut text = String::new(); + let _ = bin.iter().map(|v| text.push_str(&v.to_string())); + Self(text) + } + } + } +} diff --git a/src/gateway/events.rs b/src/gateway/events.rs new file mode 100644 index 0000000..fdb7b25 --- /dev/null +++ b/src/gateway/events.rs @@ -0,0 +1,160 @@ +use super::*; +use crate::types; + +#[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/gateway.rs b/src/gateway/gateway.rs index 30d0610..684b9d2 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,5 +1,13 @@ +use std::time::Duration; + +use futures_util::{SinkExt, StreamExt}; +use log::*; +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; + use self::event::Events; use super::*; +use super::{Sink, Stream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, @@ -10,15 +18,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: Stream, kill_send: tokio::sync::broadcast::Sender<()>, store: Arc>>>>, url: String, @@ -26,35 +27,9 @@ 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(); + pub async fn spawn(websocket_url: String) -> Result { + let (websocket_send, mut websocket_receive) = + WebSocketBackend::connect(&websocket_url).await?; let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); @@ -63,9 +38,11 @@ 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(); + #[cfg(not(target_arch = "wasm32"))] + let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + #[cfg(target_arch = "wasm32")] + let msg: GatewayMessage = websocket_receive.next().await.unwrap().into(); + let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { return Err(GatewayError::NonHelloOnInitiate { @@ -98,9 +75,14 @@ impl Gateway { }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + #[cfg(not(target_arch = "wasm32"))] task::spawn(async move { gateway.gateway_listen_task().await; }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + gateway.gateway_listen_task().await; + }); Ok(GatewayHandle { url: websocket_url.clone(), @@ -118,10 +100,16 @@ impl Gateway { loop { let msg = self.websocket_receive.next().await; + // PRETTYFYME: Remove inline conditional compiling // This if chain can be much better but if let is unstable on stable rust + #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { - self.handle_message(GatewayMessage::from_tungstenite_message(message)) - .await; + self.handle_message(message.into()).await; + continue; + } + #[cfg(target_arch = "wasm32")] + if let Some(message) = msg { + self.handle_message(message.into()).await; continue; } @@ -156,31 +144,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..620faba 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -1,3 +1,8 @@ +use futures_util::SinkExt; +use log::*; + +use std::fmt::Debug; + use super::{event::Events, *}; use crate::types::{self, Composite}; @@ -9,14 +14,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 +30,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 00fa090..da70e22 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -1,6 +1,14 @@ -use crate::types; +use futures_util::SinkExt; +use log::*; +use std::time::{self, Duration, Instant}; +use tokio::sync::mpsc::{Receiver, Sender}; + +use safina_timer::sleep_until; +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; use super::*; +use crate::types; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms pub const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; @@ -13,40 +21,29 @@ pub(super) struct HeartbeatHandler { pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, } impl HeartbeatHandler { 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; + #[cfg(not(target_arch = "wasm32"))] + task::spawn(async move { + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); Self { heartbeat_interval, send, - handle, } } @@ -55,22 +52,17 @@ 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 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"); @@ -122,9 +114,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..2c12e48 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() + serde_json::from_str(&self.0) } } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 3a14a23..a4f27a1 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,8 +1,13 @@ +use async_trait::async_trait; + +pub mod backends; +pub mod events; pub mod gateway; pub mod handle; pub mod heartbeat; pub mod message; +pub use backends::*; pub use gateway::*; pub use handle::*; use heartbeat::*; @@ -11,28 +16,11 @@ pub use 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; -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] @@ -134,54 +122,3 @@ impl GatewayEvent { } } } - -#[cfg(test)] -mod example { - 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/instance.rs b/src/instance.rs index 72bf350..4ce4338 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; @@ -138,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::new(wss_url).await.unwrap(); + let gateway = Gateway::spawn(wss_url).await.unwrap(); ChorusUser { token, belongs_to: instance.clone(), 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}; 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(), diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 280401c..5acf2ae 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -15,7 +15,10 @@ use crate::types::{ use crate::types::Composite; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::GatewayHandle; + +#[cfg(feature = "client")] +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..b3916e2 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -6,11 +6,14 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::User; use crate::types::Snowflake; +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; + #[cfg(feature = "client")] 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..eb04322 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::{GatewayHandle, Updateable}; +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 a14ef2c..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::{GatewayHandle, Updateable}; +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 087a775..6a8327e 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -9,11 +9,14 @@ 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; +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; + #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index eca5344..a7bdf63 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -5,11 +5,14 @@ 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; +#[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 74fdf8f..06bd0cc 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,13 +1,16 @@ use std::sync::{Arc, RwLock}; #[cfg(feature = "client")] -use chorus_macros::{Composite, Updateable}; +use chorus_macros::Composite; #[cfg(feature = "client")] use crate::types::Composite; #[cfg(feature = "client")] -use crate::gateway::{GatewayHandle, Updateable}; +use crate::gateway::GatewayHandle; + +#[cfg(feature = "client")] +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..cf5716c 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}; @@ -12,6 +12,9 @@ use chorus_macros::{Composite, Updateable}; #[cfg(feature = "client")] use crate::types::Composite; +#[cfg(feature = "client")] +use crate::gateway::GatewayHandle; + use crate::types::{ entities::{Guild, User}, utils::Snowflake, diff --git a/tests/auth.rs b/tests/auth.rs index f89e5e4..086c8ba 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,8 +1,14 @@ use chorus::types::RegisterSchema; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_registration() { let bundle = common::setup().await; let reg = RegisterSchema { diff --git a/tests/channels.rs b/tests/channels.rs index 1647652..864165a 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -4,8 +4,15 @@ use chorus::types::{ }; mod common; +// PRETTYFYME: Move common wasm setup to common.rs -#[tokio::test] +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channel() { let mut bundle = common::setup().await; let bundle_channel = bundle.channel.read().unwrap().clone(); @@ -18,7 +25,8 @@ async fn get_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn delete_channel() { let mut bundle = common::setup().await; let channel_guard = bundle.channel.write().unwrap().clone(); @@ -27,7 +35,8 @@ async fn delete_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn modify_channel() { const CHANNEL_NAME: &str = "beepboop"; let mut bundle = common::setup().await; @@ -85,7 +94,8 @@ async fn modify_channel() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channel_messages() { let mut bundle = common::setup().await; let channel_id: Snowflake = bundle.channel.read().unwrap().id; @@ -141,7 +151,8 @@ async fn get_channel_messages() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_dm() { let mut bundle = common::setup().await; let other_user = bundle.create_user("integrationtestuser2").await; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index ce42578..b533fd2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -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::spawn(self.instance.urls.wss.clone()) + .await + .unwrap(), } } } diff --git a/tests/gateway.rs b/tests/gateway.rs index 991c9f2..68a203a 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,24 +2,32 @@ mod common; use std::sync::{Arc, RwLock}; +use chorus::errors::GatewayError; use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection (hello and heartbeats) on the local gateway; async fn test_gateway_establish() { let bundle = common::setup().await; - Gateway::new(bundle.urls.wss.clone()).await.unwrap(); + let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection and authenticating async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = Gateway::new(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(); @@ -28,7 +36,8 @@ async fn test_gateway_authenticate() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { let mut bundle = common::setup().await; let received_channel = bundle @@ -61,7 +70,8 @@ async fn test_self_updating_structs() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_recursive_self_updating_structs() { // Setup let mut bundle = common::setup().await; @@ -116,3 +126,92 @@ async fn test_recursive_self_updating_structs() { assert_eq!(guild_role_inner.name, "yippieee".to_string()); common::teardown(bundle).await; } + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn test_error() { + let error = GatewayMessage("4000".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Unknown); + let error = GatewayMessage("4001".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::UnknownOpcode); + let error = GatewayMessage("4002".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Decode); + let error = GatewayMessage("4003".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::NotAuthenticated); + let error = GatewayMessage("4004".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::AuthenticationFailed); + let error = GatewayMessage("4005".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::AlreadyAuthenticated); + let error = GatewayMessage("4007".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidSequenceNumber); + let error = GatewayMessage("4008".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::RateLimited); + let error = GatewayMessage("4009".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::SessionTimedOut); + let error = GatewayMessage("4010".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidShard); + let error = GatewayMessage("4011".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::ShardingRequired); + let error = GatewayMessage("4012".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidAPIVersion); + let error = GatewayMessage("4013".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); + let error = GatewayMessage("4014".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::DisallowedIntents); +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn test_error_message() { + let error = GatewayMessage("Unknown Error".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Unknown); + let error = GatewayMessage("Unknown Opcode".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::UnknownOpcode); + let error = GatewayMessage("Decode Error".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::Decode); + let error = GatewayMessage("Not Authenticated".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::NotAuthenticated); + let error = GatewayMessage("Authentication Failed".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::AuthenticationFailed); + let error = GatewayMessage("Already Authenticated".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::AlreadyAuthenticated); + let error = GatewayMessage("Invalid Seq".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidSequenceNumber); + let error = GatewayMessage("Rate Limited".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::RateLimited); + let error = GatewayMessage("Session Timed Out".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::SessionTimedOut); + let error = GatewayMessage("Invalid Shard".to_string()).error().unwrap(); + assert_eq!(error, GatewayError::InvalidShard); + let error = GatewayMessage("Sharding Required".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::ShardingRequired); + let error = GatewayMessage("Invalid API Version".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidAPIVersion); + let error = GatewayMessage("Invalid Intent(s)".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); + let error = GatewayMessage("Disallowed Intent(s)".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::DisallowedIntents); + // Also test the dot thing + let error = GatewayMessage("Invalid Intent(s).".to_string()) + .error() + .unwrap(); + assert_eq!(error, GatewayError::InvalidIntents); +} diff --git a/tests/guilds.rs b/tests/guilds.rs index d7e2699..360113d 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -3,8 +3,14 @@ use chorus::types::{ }; mod common; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_creation_deletion() { let mut bundle = common::setup().await; @@ -26,7 +32,8 @@ async fn guild_creation_deletion() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_channels() { let mut bundle = common::setup().await; let guild = bundle.guild.read().unwrap().clone(); @@ -34,7 +41,8 @@ async fn get_channels() { common::teardown(bundle).await; } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_create_ban() { // TODO: When routes exist to check if user x is on guild y, add this as an assertion to check // if Spacebar actually bans the user. @@ -71,7 +79,8 @@ async fn guild_create_ban() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn modify_guild() { let mut bundle = common::setup().await; let schema = GuildModifySchema { @@ -86,7 +95,8 @@ async fn modify_guild() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn guild_remove_member() { let mut bundle = common::setup().await; let channel = bundle.channel.read().unwrap().clone(); diff --git a/tests/instance.rs b/tests/instance.rs index d3cd5f0..56f4d6d 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -1,6 +1,12 @@ mod common; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn generate_general_configuration_schema() { let bundle = common::setup().await; bundle diff --git a/tests/invites.rs b/tests/invites.rs index ab264d4..d830ee8 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,6 +1,13 @@ mod common; use chorus::types::CreateChannelInviteSchema; -#[tokio::test] +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_accept_invite() { let mut bundle = common::setup().await; let channel = bundle.channel.read().unwrap().clone(); diff --git a/tests/members.rs b/tests/members.rs index fbab772..c9072ef 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -1,8 +1,14 @@ use chorus::{errors::ChorusResult, types::GuildMember}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn add_remove_role() -> ChorusResult<()> { let mut bundle = common::setup().await; let guild = bundle.guild.read().unwrap().id; diff --git a/tests/messages.rs b/tests/messages.rs index 5ad9a89..fc59a42 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -2,10 +2,16 @@ use std::fs::File; use std::io::{BufReader, Read}; use chorus::types::{self, Guild, Message, MessageSearchQuery}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn send_message() { let mut bundle = common::setup().await; let message = types::MessageSendSchema { @@ -17,8 +23,12 @@ async fn send_message() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn send_message_attachment() { + /// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files. + #[cfg(target_arch = "wasm32")] + return; let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); let mut buffer = Vec::new(); @@ -54,8 +64,12 @@ async fn send_message_attachment() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn search_messages() { + /// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files. + #[cfg(target_arch = "wasm32")] + return; let f = File::open("./README.md").unwrap(); let mut reader = BufReader::new(f); let mut buffer = Vec::new(); @@ -100,7 +114,8 @@ async fn search_messages() { assert_eq!(query_result.get(0).unwrap().id, message.id); } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_stickies() { let mut bundle = common::setup().await; let message = types::MessageSendSchema { diff --git a/tests/relationships.rs b/tests/relationships.rs index 09ddab0..156f6eb 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,8 +1,14 @@ use chorus::types::{self, Relationship, RelationshipType}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_get_mutual_relationships() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -23,7 +29,8 @@ async fn test_get_mutual_relationships() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_get_relationships() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -46,7 +53,8 @@ async fn test_get_relationships() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_modify_relationship_friends() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; @@ -97,7 +105,8 @@ async fn test_modify_relationship_friends() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_modify_relationship_block() { let mut bundle = common::setup().await; let mut other_user = bundle.create_user("integrationtestuser2").await; diff --git a/tests/roles.rs b/tests/roles.rs index 8691138..ca58582 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -1,8 +1,14 @@ use chorus::types::{self, RoleCreateModifySchema, RoleObject}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); mod common; -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn create_and_get_roles() { let mut bundle = common::setup().await; let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS; @@ -31,7 +37,8 @@ async fn create_and_get_roles() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_and_delete_role() { let mut bundle = common::setup().await; let guild_id = bundle.guild.read().unwrap().id; diff --git a/tests/wasm.rs b/tests/wasm.rs new file mode 100644 index 0000000..d5d26c1 --- /dev/null +++ b/tests/wasm.rs @@ -0,0 +1,7 @@ +use wasm_bindgen_test::wasm_bindgen_test; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[wasm_bindgen_test] +fn pass() { + let _ = String::new(); +}