diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c0e314f..cefd260 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -45,7 +45,7 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - macos: + wasm-safari: runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -73,5 +73,59 @@ jobs: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-gecko: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + wasm-chrome: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: server/package-lock.json + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7c74e2a..fea0bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ dependencies = [ "tokio-tungstenite", "url", "wasm-bindgen", + "wasm-bindgen-futures", "wasm-bindgen-test", "ws_stream_wasm", ] diff --git a/Cargo.toml b/Cargo.toml index 24a7114..8ade54c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ hostname = "0.3.1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.11", features = ["js"] } ws_stream_wasm = "0.7.4" - +wasm-bindgen-futures = "0.4.38" [dev-dependencies] lazy_static = "1.4.0" diff --git a/README.md b/README.md index e654610..e435086 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ dbg!(&user.object.read().unwrap().username); ## Supported Platforms All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported. -We are currently working on adding full support for `wasm32-unknown-unknown`. This will allow you to use Chorus in +`wasm32-unknown-unknown` is a supported compilation target on versions `>0.11.0`. This allows you to use Chorus in your browser, or in any other environment that supports WebAssembly. We recommend checking out the examples directory, as well as the documentation for more information. @@ -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/src/gateway/gateway.rs b/src/gateway/gateway.rs index d10dbfb..684b9d2 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -2,6 +2,7 @@ use std::time::Duration; use futures_util::{SinkExt, StreamExt}; use log::*; +#[cfg(not(target_arch = "wasm32"))] use tokio::task; use self::event::Events; @@ -74,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(), diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index b8e4bec..1176517 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -4,7 +4,8 @@ use std::time::{self, Duration, Instant}; use tokio::sync::mpsc::{Receiver, Sender}; use safina_timer::sleep_until; -use tokio::task::{self, JoinHandle}; +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; use super::*; use crate::types; @@ -20,8 +21,6 @@ pub(super) struct HeartbeatHandler { pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, - /// The handle of the thread - handle: JoinHandle<()>, } impl HeartbeatHandler { @@ -33,14 +32,18 @@ impl HeartbeatHandler { let (send, receive) = tokio::sync::mpsc::channel(32); let kill_receive = kill_rc.resubscribe(); - let handle: JoinHandle<()> = task::spawn(async move { + #[cfg(not(target_arch = "wasm32"))] + task::spawn(async move { + Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await; }); Self { heartbeat_interval, send, - handle, } } 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/gateway.rs b/tests/gateway.rs index 0b1e12f..b564cde 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -4,8 +4,14 @@ use std::sync::{Arc, RwLock}; use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +// PRETTYFYME: Move common wasm setup to common.rs +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); -#[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; @@ -14,7 +20,8 @@ async fn test_gateway_establish() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection and authenticating async fn test_gateway_authenticate() { let bundle = common::setup().await; @@ -28,7 +35,8 @@ async fn test_gateway_authenticate() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { let mut bundle = common::setup().await; let received_channel = bundle @@ -61,7 +69,8 @@ async fn test_self_updating_structs() { common::teardown(bundle).await } -#[tokio::test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_recursive_self_updating_structs() { // Setup let mut bundle = common::setup().await; diff --git a/tests/guilds.rs b/tests/guilds.rs index 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;