diff --git a/.travis.yml b/.travis.yml index b210cbf8..7667149b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,28 @@ jobs: after_success: - bash <(curl -s https://codecov.io/bash) + - os: linux + name: wasm32-unknown-unknown + before_script: + - | + cargo install wasm-bindgen-cli + rustup target add wasm32-unknown-unknown + wget https://github.com/emscripten-core/emsdk/archive/master.zip + unzip master.zip + ./emsdk-master/emsdk install latest + ./emsdk-master/emsdk activate latest + script: + - | + source emsdk-master/emsdk_env.sh + cd matrix_sdk/examples/wasm_command_bot + cargo build --target wasm32-unknown-unknown + cd - + + cd matrix_sdk_base + cargo test --target wasm32-unknown-unknown --no-default-features + + + before_script: - rustup component add rustfmt diff --git a/Cargo.toml b/Cargo.toml index 2ec87410..57e18193 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "matrix_sdk", "matrix_sdk_base", "matrix_sdk_test", + "matrix_sdk_test_macros", "matrix_sdk_crypto", "matrix_sdk_common", ] diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index 53af0ca9..8c36b379 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -17,14 +17,14 @@ encryption = ["matrix-sdk-base/encryption"] sqlite-cryptostore = ["matrix-sdk-base/sqlite-cryptostore"] [dependencies] -futures = "0.3.4" http = "0.2.1" reqwest = "0.10.4" serde_json = "1.0.52" thiserror = "1.0.16" tracing = "0.1.13" url = "2.1.1" -uuid = { version = "0.8.1", features = ["v4"] } +futures-timer = "3.0.2" + matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } @@ -38,11 +38,6 @@ version = "0.2.4" default-features = false features = ["std", "std-future"] -[dependencies.tokio] -version = "0.2.20" -default-features = false -features = ["time"] - [dev-dependencies] async-trait = "0.1.30" dirs = "2.0.2" @@ -54,3 +49,4 @@ tracing-subscriber = "0.2.5" tempfile = "3.1.0" mockito = "0.25.1" lazy_static = "1.4.0" +futures = "0.3.4" diff --git a/matrix_sdk/examples/wasm_command_bot/.gitignore b/matrix_sdk/examples/wasm_command_bot/.gitignore new file mode 100644 index 00000000..43d61f45 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/.gitignore @@ -0,0 +1,7 @@ +node_modules +pkg +target +Cargo.lock +*.swp +dist +package-lock.json diff --git a/matrix_sdk/examples/wasm_command_bot/Cargo.toml b/matrix_sdk/examples/wasm_command_bot/Cargo.toml new file mode 100644 index 00000000..14c3ab92 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wasm" +version = "0.1.0" +authors = ["stoically "] +edition = "2018" + +# Config mostly pulled from: https://github.com/rustwasm/wasm-bindgen/blob/master/examples/fetch/Cargo.toml + +[lib] +crate-type = ["cdylib"] + +[dependencies] +matrix-sdk = { path = "../..", default-features = false } +url = "2.1.1" +wasm-bindgen = { version = "0.2.62", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.12" +web-sys = { version = "0.3.39", features = ["console"] } + +[workspace] diff --git a/matrix_sdk/examples/wasm_command_bot/README.md b/matrix_sdk/examples/wasm_command_bot/README.md new file mode 100644 index 00000000..49d1c688 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/README.md @@ -0,0 +1,12 @@ +## Example usage of matrix-rust-sdk from WASM + +You can build the example locally with: + + npm install + npm run serve + +and then visiting http://localhost:8080 in a browser should run the example! + +Note: Encryption isn't supported yet + +This example is loosely based off of [this example](https://github.com/seanmonstar/reqwest/tree/master/examples/wasm_github_fetch), an example usage of `fetch` from `wasm-bindgen`. \ No newline at end of file diff --git a/matrix_sdk/examples/wasm_command_bot/index.js b/matrix_sdk/examples/wasm_command_bot/index.js new file mode 100644 index 00000000..1fd262d0 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/index.js @@ -0,0 +1,7 @@ +const rust = import('./pkg'); + +rust + .then(m => { + return m.run() + }) + .catch(console.error); diff --git a/matrix_sdk/examples/wasm_command_bot/package.json b/matrix_sdk/examples/wasm_command_bot/package.json new file mode 100644 index 00000000..2a84b1f7 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "html-webpack-plugin": "^3.2.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/matrix_sdk/examples/wasm_command_bot/src/lib.rs b/matrix_sdk/examples/wasm_command_bot/src/lib.rs new file mode 100644 index 00000000..3b1cf231 --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/src/lib.rs @@ -0,0 +1,74 @@ +use matrix_sdk::{ + api::r0::sync::sync_events::Response as SyncResponse, + events::collections::all::RoomEvent, + events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + identifiers::RoomId, + Client, ClientConfig, SyncSettings, +}; +use url::Url; +use wasm_bindgen::prelude::*; +use web_sys::console; + +struct WasmBot(Client); + +impl WasmBot { + async fn on_room_message(&self, room_id: &RoomId, event: RoomEvent) { + let msg_body = if let RoomEvent::RoomMessage(MessageEvent { + content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), + .. + }) = event + { + msg_body.clone() + } else { + return; + }; + + console::log_1(&format!("Received message event {:?}", &msg_body).into()); + + if msg_body.starts_with("!party") { + let content = MessageEventContent::Text(TextMessageEventContent::new_plain( + "🎉🎊🥳 let's PARTY with wasm!! 🥳🎊🎉".to_string(), + )); + + self.0.room_send(&room_id, content, None).await.unwrap(); + } + } + async fn on_sync_response(&self, response: SyncResponse) { + console::log_1(&format!("Synced").into()); + + for (room_id, room) in response.rooms.join { + for event in room.timeline.events { + if let Ok(event) = event.deserialize() { + self.on_room_message(&room_id, event).await + } + } + } + } +} + +#[wasm_bindgen] +pub async fn run() -> Result { + let homeserver_url = "http://localhost:8008"; + let username = "user"; + let password = "password"; + + let client_config = ClientConfig::new(); + let homeserver_url = Url::parse(&homeserver_url).unwrap(); + let client = Client::new_with_config(homeserver_url, None, client_config).unwrap(); + + client + .login(username, password, None, Some("rust-sdk-wasm")) + .await + .unwrap(); + + let bot = WasmBot(client.clone()); + + client.sync(SyncSettings::default()).await.unwrap(); + + let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + client + .sync_forever(settings, |response| bot.on_sync_response(response)) + .await; + + Ok(JsValue::NULL) +} diff --git a/matrix_sdk/examples/wasm_command_bot/webpack.config.js b/matrix_sdk/examples/wasm_command_bot/webpack.config.js new file mode 100644 index 00000000..e1cee3ee --- /dev/null +++ b/matrix_sdk/examples/wasm_command_bot/webpack.config.js @@ -0,0 +1,26 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); + +module.exports = { + entry: './index.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'index.js', + }, + devtool: 'source-map', + plugins: [ + new HtmlWebpackPlugin(), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, ".") + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development' +}; diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 9224e36c..604604d6 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -19,20 +19,20 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::result::Result as StdResult; use std::sync::Arc; -use std::time::{Duration, Instant}; -use uuid::Uuid; +use matrix_sdk_common::instant::{Duration, Instant}; +use matrix_sdk_common::locks::RwLock; +use matrix_sdk_common::uuid::Uuid; -use futures::future::Future; -use tokio::sync::RwLock; -use tokio::time::delay_for as sleep; +use futures_timer::Delay as sleep; +use std::future::Future; #[cfg(feature = "encryption")] use tracing::{debug, warn}; use tracing::{info, instrument, trace}; use http::Method as HttpMethod; use http::Response as HttpResponse; -use reqwest::header::{HeaderValue, InvalidHeaderValue}; +use reqwest::header::{HeaderValue, InvalidHeaderValue, AUTHORIZATION}; use url::Url; use crate::events::room::message::MessageEventContent; @@ -98,6 +98,7 @@ impl std::fmt::Debug for Client { /// .state_store(Box::new(store)); /// ``` pub struct ClientConfig { + #[cfg(not(target_arch = "wasm32"))] proxy: Option, user_agent: Option, disable_ssl_verification: bool, @@ -106,9 +107,12 @@ pub struct ClientConfig { impl std::fmt::Debug for ClientConfig { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { - fmt.debug_struct("ClientConfig") - .field("proxy", &self.proxy) - .field("user_agent", &self.user_agent) + let mut res = fmt.debug_struct("ClientConfig"); + + #[cfg(not(target_arch = "wasm32"))] + let res = res.field("proxy", &self.proxy); + + res.field("user_agent", &self.user_agent) .field("disable_ssl_verification", &self.disable_ssl_verification) .finish() } @@ -137,6 +141,7 @@ impl ClientConfig { /// .proxy("http://localhost:8080") /// .unwrap(); /// ``` + #[cfg(not(target_arch = "wasm32"))] pub fn proxy(mut self, proxy: &str) -> Result { self.proxy = Some(reqwest::Proxy::all(proxy)?); Ok(self) @@ -262,27 +267,32 @@ impl Client { let http_client = reqwest::Client::builder(); - let http_client = if config.disable_ssl_verification { - http_client.danger_accept_invalid_certs(true) - } else { - http_client + #[cfg(not(target_arch = "wasm32"))] + let http_client = { + let http_client = if config.disable_ssl_verification { + http_client.danger_accept_invalid_certs(true) + } else { + http_client + }; + + let http_client = match config.proxy { + Some(p) => http_client.proxy(p), + None => http_client, + }; + + let mut headers = reqwest::header::HeaderMap::new(); + + let user_agent = match config.user_agent { + Some(a) => a, + None => HeaderValue::from_str(&format!("matrix-rust-sdk {}", VERSION)).unwrap(), + }; + + headers.insert(reqwest::header::USER_AGENT, user_agent); + + http_client.default_headers(headers) }; - let http_client = match config.proxy { - Some(p) => http_client.proxy(p), - None => http_client, - }; - - let mut headers = reqwest::header::HeaderMap::new(); - - let user_agent = match config.user_agent { - Some(a) => a, - None => HeaderValue::from_str(&format!("matrix-rust-sdk {}", VERSION)).unwrap(), - }; - - headers.insert(reqwest::header::USER_AGENT, user_agent); - - let http_client = http_client.default_headers(headers).build()?; + let http_client = http_client.build()?; let base_client = if let Some(store) = config.state_store { BaseClient::new_with_state_store(session, store)? @@ -317,21 +327,21 @@ impl Client { /// Returns the joined rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn joined_rooms(&self) -> Arc>>>> { + pub fn joined_rooms(&self) -> Arc>>>> { self.base_client.joined_rooms() } /// Returns the invited rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn invited_rooms(&self) -> Arc>>>> { + pub fn invited_rooms(&self) -> Arc>>>> { self.base_client.invited_rooms() } /// Returns the left rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn left_rooms(&self) -> Arc>>>> { + pub fn left_rooms(&self) -> Arc>>>> { self.base_client.left_rooms() } @@ -848,12 +858,12 @@ impl Client { let response = if let Ok(r) = response { r } else { - sleep(Duration::from_secs(1)).await; + #[cfg(not(target_arch = "wasm32"))] + sleep::new(Duration::from_secs(1)).await; + continue; }; - callback(response).await; - // TODO send out to-device messages here #[cfg(feature = "encryption")] @@ -875,14 +885,19 @@ impl Client { } } + callback(response).await; + let now = Instant::now(); // If the last sync happened less than a second ago, sleep for a // while to not hammer out requests if the server doesn't respect // the sync timeout. - if let Some(t) = last_sync_time { - if now - t <= Duration::from_secs(1) { - sleep(Duration::from_secs(1)).await; + #[cfg(not(target_arch = "wasm32"))] + { + if let Some(t) = last_sync_time { + if now - t <= Duration::from_secs(1) { + sleep::new(Duration::from_secs(1)).await; + } } } @@ -934,7 +949,8 @@ impl Client { let session = self.base_client.session().read().await; if let Some(session) = session.as_ref() { - request_builder.bearer_auth(&session.access_token) + let header_value = format!("Bearer {}", &session.access_token); + request_builder.header(AUTHORIZATION, header_value) } else { return Err(Error::AuthenticationRequired); } @@ -991,7 +1007,7 @@ impl Client { /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); /// # let mut client = Client::new(homeserver, None).unwrap(); /// # let room_id = RoomId::try_from("!test:localhost").unwrap(); - /// use uuid::Uuid; + /// use matrix_sdk_common::uuid::Uuid; /// /// let content = MessageEventContent::Text(TextMessageEventContent { /// body: "Hello world".to_owned(), @@ -1214,7 +1230,7 @@ mod test { use crate::identifiers::{EventId, RoomId, UserId}; use matrix_sdk_base::JsonStore; - use matrix_sdk_test::EventBuilder; + use matrix_sdk_test::{EventBuilder, EventsFile}; use mockito::{mock, Matcher}; use std::convert::TryFrom; @@ -1261,11 +1277,8 @@ mod test { let client = Client::new(homeserver, Some(session)).unwrap(); let mut response = EventBuilder::default() - .add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file( - "../test_data/events/power_levels.json", - RoomEvent::RoomPowerLevels, - ) + .add_room_event(EventsFile::Member, RoomEvent::RoomMember) + .add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels) .build_sync_response(); client @@ -1662,7 +1675,7 @@ mod test { #[tokio::test] async fn room_message_send() { - use uuid::Uuid; + use matrix_sdk_common::uuid::Uuid; let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let user = UserId::try_from("@example:localhost").unwrap(); diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index a2043d39..e39ae724 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -26,8 +26,10 @@ //! destroyed. #![deny(missing_docs)] -pub use matrix_sdk_base::{AllRooms, JsonStore, RoomState, StateStore}; +#[cfg(not(target_arch = "wasm32"))] +pub use matrix_sdk_base::JsonStore; pub use matrix_sdk_base::{EventEmitter, Room, Session, SyncRoom}; +pub use matrix_sdk_base::{RoomState, StateStore}; pub use matrix_sdk_common::*; pub use reqwest::header::InvalidHeaderValue; diff --git a/matrix_sdk_base/.cargo/config b/matrix_sdk_base/.cargo/config new file mode 100644 index 00000000..0e68dad1 --- /dev/null +++ b/matrix_sdk_base/.cargo/config @@ -0,0 +1,3 @@ +# required because of https://github.com/rustwasm/wasm-pack/issues/698 +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/matrix_sdk_base/Cargo.toml b/matrix_sdk_base/Cargo.toml index 97ab1c65..2b98df27 100644 --- a/matrix_sdk_base/Cargo.toml +++ b/matrix_sdk_base/Cargo.toml @@ -17,7 +17,6 @@ encryption = ["matrix-sdk-crypto"] sqlite-cryptostore = ["matrix-sdk-crypto/sqlite-cryptostore"] [dependencies] -futures = "0.3.4" async-trait = "0.1.30" serde = "1.0.106" serde_json = "1.0.52" @@ -28,15 +27,20 @@ matrix-sdk-crypto = { version = "0.1.0", path = "../matrix_sdk_crypto", optional # Misc dependencies thiserror = "1.0.16" -[dependencies.tokio] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] version = "0.2.20" default-features = false features = ["sync", "fs"] [dev-dependencies] matrix-sdk-test = { version = "0.1.0", path = "../matrix_sdk_test" } -tokio = { version = "0.2.20", features = ["rt-threaded", "macros"] } http = "0.2.1" dirs = "2.0.2" tracing-subscriber = "0.2.5" tempfile = "3.1.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +tokio = { version = "0.2.20", features = ["rt-threaded", "macros"] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3.12" \ No newline at end of file diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 7d6a126c..541b7af4 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -39,10 +39,10 @@ use crate::session::Session; use crate::state::{AllRooms, ClientState, StateStore}; use crate::EventEmitter; -use std::ops::Deref; #[cfg(feature = "encryption")] -use tokio::sync::Mutex; -use tokio::sync::RwLock; +use matrix_sdk_common::locks::Mutex; +use matrix_sdk_common::locks::RwLock; +use std::ops::Deref; #[cfg(feature = "encryption")] use crate::api::r0::keys::{ @@ -337,7 +337,7 @@ impl BaseClient { /// Returns the joined rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn joined_rooms(&self) -> Arc>>>> { + pub fn joined_rooms(&self) -> Arc>>>> { self.joined_rooms.clone() } @@ -371,7 +371,7 @@ impl BaseClient { /// Returns the invited rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn invited_rooms(&self) -> Arc>>>> { + pub fn invited_rooms(&self) -> Arc>>>> { self.invited_rooms.clone() } @@ -405,7 +405,7 @@ impl BaseClient { /// Returns the left rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub fn left_rooms(&self) -> Arc>>>> { + pub fn left_rooms(&self) -> Arc>>>> { self.left_rooms.clone() } diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index 162f04cd..5c3a55b0 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -170,8 +170,13 @@ pub trait EventEmitter: Send + Sync { #[cfg(test)] mod test { use super::*; + use matrix_sdk_common::locks::Mutex; + use matrix_sdk_test::{async_test, sync_response, SyncResponseFile}; use std::sync::Arc; - use tokio::sync::Mutex; + + #[cfg(target_arch = "wasm32")] + pub use wasm_bindgen_test::*; + #[derive(Clone)] pub struct EvEmitterTest(Arc>>); @@ -285,22 +290,10 @@ mod test { } } - use crate::api::r0::sync::sync_events::Response as SyncResponse; use crate::identifiers::UserId; use crate::{BaseClient, Session}; - use http::Response; use std::convert::TryFrom; - use std::fs::File; - use std::io::Read; - - fn sync_response(file: &str) -> SyncResponse { - let mut file = File::open(file).unwrap(); - let mut data = vec![]; - file.read_to_end(&mut data).unwrap(); - let response = Response::builder().body(data).unwrap(); - SyncResponse::try_from(response).unwrap() - } fn get_client() -> BaseClient { let session = Session { @@ -311,7 +304,7 @@ mod test { BaseClient::new(Some(session)).unwrap() } - #[tokio::test] + #[async_test] async fn event_emitter_joined() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); @@ -320,7 +313,7 @@ mod test { let client = get_client(); client.add_event_emitter(emitter).await; - let mut response = sync_response("../test_data/sync.json"); + let mut response = sync_response(SyncResponseFile::Default); client.receive_sync_response(&mut response).await.unwrap(); let v = test_vec.lock().await; @@ -342,7 +335,7 @@ mod test { ) } - #[tokio::test] + #[async_test] async fn event_emitter_invite() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); @@ -351,7 +344,7 @@ mod test { let client = get_client(); client.add_event_emitter(emitter).await; - let mut response = sync_response("../test_data/invite_sync.json"); + let mut response = sync_response(SyncResponseFile::Invite); client.receive_sync_response(&mut response).await.unwrap(); let v = test_vec.lock().await; @@ -361,7 +354,7 @@ mod test { ) } - #[tokio::test] + #[async_test] async fn event_emitter_leave() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); @@ -370,7 +363,7 @@ mod test { let client = get_client(); client.add_event_emitter(emitter).await; - let mut response = sync_response("../test_data/leave_sync.json"); + let mut response = sync_response(SyncResponseFile::Leave); client.receive_sync_response(&mut response).await.unwrap(); let v = test_vec.lock().await; diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 0d386a37..9c61b2c1 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -41,4 +41,6 @@ pub use event_emitter::{EventEmitter, SyncRoom}; #[cfg(feature = "encryption")] pub use matrix_sdk_crypto::{Device, TrustState}; pub use models::Room; -pub use state::{AllRooms, JsonStore, StateStore}; +#[cfg(not(target_arch = "wasm32"))] +pub use state::JsonStore; +pub use state::StateStore; diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index 47c2acdb..0262b757 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -143,6 +143,9 @@ mod test { use std::collections::HashMap; use std::convert::TryFrom; + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + use crate::events::{collections::all::RoomEvent, EventJson}; use crate::identifiers::{RoomId, UserId}; use crate::Room; diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index f2de36b0..44912eb1 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -521,27 +521,17 @@ impl Room { #[cfg(test)] mod test { use super::*; - use crate::api::r0::sync::sync_events::Response as SyncResponse; use crate::events::room::member::MembershipState; use crate::identifiers::UserId; use crate::{BaseClient, Session}; - use matrix_sdk_test::EventBuilder; + use matrix_sdk_test::{async_test, sync_response, EventBuilder, EventsFile, SyncResponseFile}; - use http::Response; + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; use std::convert::TryFrom; - use std::fs::File; - use std::io::Read; use std::ops::Deref; - fn sync_response(file: &str) -> SyncResponse { - let mut file = File::open(file).unwrap(); - let mut data = vec![]; - file.read_to_end(&mut data).unwrap(); - let response = Response::builder().body(data).unwrap(); - SyncResponse::try_from(response).unwrap() - } - fn get_client() -> BaseClient { let session = Session { access_token: "1234".to_owned(), @@ -555,11 +545,11 @@ mod test { RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap() } - #[tokio::test] + #[async_test] async fn user_presence() { let client = get_client(); - let mut response = sync_response("../test_data/sync.json"); + let mut response = sync_response(SyncResponseFile::Default); client.receive_sync_response(&mut response).await.unwrap(); @@ -579,18 +569,15 @@ mod test { assert!(room.deref().power_levels.is_some()) } - #[tokio::test] + #[async_test] async fn room_events() { let client = get_client(); let room_id = get_room_id(); let user_id = UserId::try_from("@example:localhost").unwrap(); let mut response = EventBuilder::default() - .add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file( - "../test_data/events/power_levels.json", - RoomEvent::RoomPowerLevels, - ) + .add_room_event(EventsFile::Member, RoomEvent::RoomMember) + .add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); @@ -611,14 +598,14 @@ mod test { ); } - #[tokio::test] + #[async_test] async fn calculate_aliases() { let client = get_client(); let room_id = get_room_id(); let mut response = EventBuilder::default() - .add_state_event_from_file("../test_data/events/aliases.json", StateEvent::RoomAliases) + .add_state_event(EventsFile::Aliases, StateEvent::RoomAliases) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); @@ -629,17 +616,14 @@ mod test { assert_eq!("tutorial", room.display_name()); } - #[tokio::test] + #[async_test] async fn calculate_alias() { let client = get_client(); let room_id = get_room_id(); let mut response = EventBuilder::default() - .add_state_event_from_file( - "../test_data/events/alias.json", - StateEvent::RoomCanonicalAlias, - ) + .add_state_event(EventsFile::Alias, StateEvent::RoomCanonicalAlias) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); @@ -650,14 +634,14 @@ mod test { assert_eq!("tutorial", room.display_name()); } - #[tokio::test] + #[async_test] async fn calculate_name() { let client = get_client(); let room_id = get_room_id(); let mut response = EventBuilder::default() - .add_state_event_from_file("../test_data/events/name.json", StateEvent::RoomName) + .add_state_event(EventsFile::Name, StateEvent::RoomName) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); @@ -668,9 +652,9 @@ mod test { assert_eq!("room name", room.display_name()); } - #[tokio::test] + #[async_test] async fn calculate_room_names_from_summary() { - let mut response = sync_response("../test_data/sync_with_summary.json"); + let mut response = sync_response(SyncResponseFile::DefaultWithSummary); let session = Session { access_token: "1234".to_owned(), diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index afedb5a2..8050a75c 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -199,7 +199,7 @@ impl RoomMember { #[cfg(test)] mod test { - use matrix_sdk_test::EventBuilder; + use matrix_sdk_test::{async_test, EventBuilder, EventsFile}; use crate::events::collections::all::RoomEvent; use crate::events::room::member::MembershipState; @@ -208,6 +208,9 @@ mod test { use crate::js_int::Int; + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + use std::convert::TryFrom; fn get_client() -> BaseClient { @@ -223,18 +226,15 @@ mod test { RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap() } - #[tokio::test] + #[async_test] async fn room_member_events() { let client = get_client(); let room_id = get_room_id(); let mut response = EventBuilder::default() - .add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file( - "../test_data/events/power_levels.json", - RoomEvent::RoomPowerLevels, - ) + .add_room_event(EventsFile::Member, RoomEvent::RoomMember) + .add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); @@ -250,19 +250,16 @@ mod test { assert_eq!(member.power_level, Int::new(100)); } - #[tokio::test] + #[async_test] async fn member_presence_events() { let client = get_client(); let room_id = get_room_id(); let mut response = EventBuilder::default() - .add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file( - "../test_data/events/power_levels.json", - RoomEvent::RoomPowerLevels, - ) - .add_presence_event_from_file("../test_data/events/presence.json") + .add_room_event(EventsFile::Member, RoomEvent::RoomMember) + .add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels) + .add_presence_event(EventsFile::Presence) .build_sync_response(); client.receive_sync_response(&mut response).await.unwrap(); diff --git a/matrix_sdk_base/src/state/mod.rs b/matrix_sdk_base/src/state/mod.rs index ca2fb04c..c7909bf5 100644 --- a/matrix_sdk_base/src/state/mod.rs +++ b/matrix_sdk_base/src/state/mod.rs @@ -15,8 +15,11 @@ use serde::{Deserialize, Serialize}; +#[cfg(not(target_arch = "wasm32"))] pub mod state_store; -pub use state_store::{AllRooms, JsonStore}; +pub use state_store::AllRooms; +#[cfg(not(target_arch = "wasm32"))] +pub use state_store::JsonStore; use crate::client::{BaseClient, Token}; use crate::events::push_rules::Ruleset; diff --git a/matrix_sdk_base/src/state/state_store.rs b/matrix_sdk_base/src/state/state_store.rs index fe66c29e..5645d075 100644 --- a/matrix_sdk_base/src/state/state_store.rs +++ b/matrix_sdk_base/src/state/state_store.rs @@ -6,9 +6,9 @@ use std::sync::{ Arc, }; +use matrix_sdk_common::locks::RwLock; use tokio::fs as async_fs; use tokio::io::AsyncWriteExt; -use tokio::sync::RwLock; use super::{ClientState, StateStore}; use crate::identifiers::RoomId; diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 4dcfbb16..a4875493 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -16,3 +16,16 @@ ruma-api = "0.16.0" ruma-client-api = "0.8.0" ruma-events = "0.21.0" ruma-identifiers = "0.16.1" +instant = { version = "0.1.3", features = ["wasm-bindgen", "now"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +uuid = { version = "0.8.1", features = ["v4"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] +version = "0.2.20" +default-features = false +features = ["sync", "time", "fs"] + +[target.'cfg(target_arch = "wasm32")'.dependencies] +futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false } +uuid = { version = "0.8.1", features = ["v4", "wasm-bindgen"] } diff --git a/matrix_sdk_common/src/lib.rs b/matrix_sdk_common/src/lib.rs index da68b770..42cce581 100644 --- a/matrix_sdk_common/src/lib.rs +++ b/matrix_sdk_common/src/lib.rs @@ -1,3 +1,4 @@ +pub use instant; pub use js_int; pub use ruma_api::{ error::{FromHttpResponseError, IntoHttpError, ServerError}, @@ -6,3 +7,7 @@ pub use ruma_api::{ pub use ruma_client_api as api; pub use ruma_events as events; pub use ruma_identifiers as identifiers; + +pub use uuid; + +pub mod locks; diff --git a/matrix_sdk_common/src/locks.rs b/matrix_sdk_common/src/locks.rs new file mode 100644 index 00000000..6f648a39 --- /dev/null +++ b/matrix_sdk_common/src/locks.rs @@ -0,0 +1,13 @@ +// could switch to futures-lock completely at some point, blocker: +// https://github.com/asomers/futures-locks/issues/34 +// https://www.reddit.com/r/rust/comments/f4zldz/i_audited_3_different_implementation_of_async/ + +#[cfg(target_arch = "wasm32")] +pub use futures_locks::Mutex; +#[cfg(target_arch = "wasm32")] +pub use futures_locks::RwLock; + +#[cfg(not(target_arch = "wasm32"))] +pub use tokio::sync::Mutex; +#[cfg(not(target_arch = "wasm32"))] +pub use tokio::sync::RwLock; diff --git a/matrix_sdk_crypto/Cargo.toml b/matrix_sdk_crypto/Cargo.toml index 96b1c057..12b9c2ab 100644 --- a/matrix_sdk_crypto/Cargo.toml +++ b/matrix_sdk_crypto/Cargo.toml @@ -15,7 +15,6 @@ default = [] sqlite-cryptostore = ["sqlx"] [dependencies] -futures = "0.3.4" async-trait = "0.1.30" matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } @@ -25,7 +24,6 @@ serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.52" cjson = "0.1.0" zeroize = { version = "1.1.0", features = ["zeroize_derive"] } -uuid = { version = "0.8.1", features = ["v4"] } url = "2.1.1" # Misc dependencies @@ -39,12 +37,7 @@ version = "0.2.4" default-features = false features = ["std", "std-future"] -[dependencies.tokio] -version = "0.2.20" -default-features = false -features = ["sync", "time"] - -[dependencies.sqlx] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.sqlx] version = "0.3.5" optional = true default-features = false diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 2c4dcd48..96a3b897 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -19,7 +19,6 @@ use std::mem; use std::path::Path; use std::result::Result as StdResult; use std::sync::atomic::{AtomicU64, Ordering}; -use uuid::Uuid; use super::error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult, SignatureError}; use super::olm::{ @@ -46,6 +45,7 @@ use matrix_sdk_common::events::{ Algorithm, EventJson, EventType, }; use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::uuid::Uuid; use api::r0::keys; use api::r0::{ diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 0735de30..2172010c 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; use std::sync::Arc; use dashmap::{DashMap, ReadOnlyView}; -use tokio::sync::Mutex; +use matrix_sdk_common::locks::Mutex; use super::device::Device; use super::olm::{InboundGroupSession, Session}; diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index 4df9741f..5e09ead1 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use matrix_sdk_common::instant::Instant; use std::fmt; use std::mem; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; -use std::time::Instant; +use matrix_sdk_common::locks::Mutex; use serde::Serialize; -use tokio::sync::Mutex; use zeroize::Zeroize; pub use olm_rs::account::IdentityKeys; diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index 0a921b85..08c1f83f 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -16,7 +16,7 @@ use std::collections::HashSet; use std::sync::Arc; use async_trait::async_trait; -use tokio::sync::Mutex; +use matrix_sdk_common::locks::Mutex; use super::{Account, CryptoStore, InboundGroupSession, Result, Session}; use crate::device::Device; diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 36d6c4e4..916a5a29 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -19,9 +19,9 @@ use std::sync::Arc; use url::ParseError; use async_trait::async_trait; +use matrix_sdk_common::locks::Mutex; use serde_json::Error as SerdeError; use thiserror::Error; -use tokio::sync::Mutex; use super::device::Device; use super::memory_stores::UserDevices; diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 9d824032..922bebb1 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -12,19 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use matrix_sdk_common::instant::{Duration, Instant}; use std::collections::{BTreeMap, HashSet}; use std::convert::TryFrom; use std::mem; use std::path::{Path, PathBuf}; use std::result::Result as StdResult; use std::sync::Arc; -use std::time::{Duration, Instant}; use url::Url; use async_trait::async_trait; +use matrix_sdk_common::locks::Mutex; use olm_rs::PicklingMode; use sqlx::{query, query_as, sqlite::SqliteQueryAs, Connect, Executor, SqliteConnection}; -use tokio::sync::Mutex; use zeroize::Zeroizing; use super::{Account, CryptoStore, CryptoStoreError, InboundGroupSession, Result, Session}; diff --git a/matrix_sdk_test/Cargo.toml b/matrix_sdk_test/Cargo.toml index c4cb3243..65650f76 100644 --- a/matrix_sdk_test/Cargo.toml +++ b/matrix_sdk_test/Cargo.toml @@ -14,3 +14,4 @@ version = "0.1.0" serde_json = "1.0.52" http = "0.2.1" matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } +matrix-sdk-test-macros = { path = "../matrix_sdk_test_macros" } \ No newline at end of file diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index 9fd49ddd..0f239e82 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -1,7 +1,5 @@ use std::convert::TryFrom; -use std::fs; use std::panic; -use std::path::Path; use http::Response; @@ -15,6 +13,34 @@ use matrix_sdk_common::events::{ EventJson, TryFromRaw, }; +pub use matrix_sdk_test_macros::async_test; + +/// Embedded event files +#[derive(Debug)] +pub enum EventsFile { + Alias, + Aliases, + Create, + FullyRead, + HistoryVisibility, + JoinRules, + Member, + MessageEmote, + MessageNotice, + MessageText, + Name, + PowerLevels, + Presence, + RedactedInvalid, + RedactedState, + Redacted, + Redaction, + RoomAvatar, + Tag, + Topic, + Typing, +} + /// Easily create events to stream into either a Client or a `Room` for testing. #[derive(Default)] pub struct EventBuilder { @@ -32,13 +58,16 @@ pub struct EventBuilder { impl EventBuilder { /// Add an event to the room events `Vec`. - pub fn add_ephemeral_from_file>( + pub fn add_ephemeral( mut self, - path: P, + file: EventsFile, variant: fn(Ev) -> Event, ) -> Self { - let val = fs::read_to_string(path.as_ref()) - .unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref())); + let val: &str = match file { + EventsFile::Typing => include_str!("../../test_data/events/typing.json"), + _ => panic!("unknown ephemeral event file {:?}", file), + }; + let event = serde_json::from_str::>(&val) .unwrap() .deserialize() @@ -48,13 +77,16 @@ impl EventBuilder { } /// Add an event to the room events `Vec`. - pub fn add_account_from_file>( + #[allow(clippy::match_single_binding, unused)] + pub fn add_account( mut self, - path: P, + file: EventsFile, variant: fn(Ev) -> Event, ) -> Self { - let val = fs::read_to_string(path.as_ref()) - .unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref())); + let val: &str = match file { + _ => panic!("unknown account event file {:?}", file), + }; + let event = serde_json::from_str::>(&val) .unwrap() .deserialize() @@ -64,13 +96,17 @@ impl EventBuilder { } /// Add an event to the room events `Vec`. - pub fn add_room_event_from_file>( + pub fn add_room_event( mut self, - path: P, + file: EventsFile, variant: fn(Ev) -> RoomEvent, ) -> Self { - let val = fs::read_to_string(path.as_ref()) - .unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref())); + let val = match file { + EventsFile::Member => include_str!("../../test_data/events/member.json"), + EventsFile::PowerLevels => include_str!("../../test_data/events/power_levels.json"), + _ => panic!("unknown room event file {:?}", file), + }; + let event = serde_json::from_str::>(&val) .unwrap() .deserialize() @@ -80,13 +116,18 @@ impl EventBuilder { } /// Add a state event to the state events `Vec`. - pub fn add_state_event_from_file>( + pub fn add_state_event( mut self, - path: P, + file: EventsFile, variant: fn(Ev) -> StateEvent, ) -> Self { - let val = fs::read_to_string(path.as_ref()) - .unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref())); + let val = match file { + EventsFile::Alias => include_str!("../../test_data/events/alias.json"), + EventsFile::Aliases => include_str!("../../test_data/events/aliases.json"), + EventsFile::Name => include_str!("../../test_data/events/name.json"), + _ => panic!("unknown state event file {:?}", file), + }; + let event = serde_json::from_str::>(&val) .unwrap() .deserialize() @@ -95,10 +136,13 @@ impl EventBuilder { self } - /// Add a presence event to the presence events `Vec`. - pub fn add_presence_event_from_file>(mut self, path: P) -> Self { - let val = fs::read_to_string(path.as_ref()) - .unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref())); + /// Add an presence event to the presence events `Vec`. + pub fn add_presence_event(mut self, file: EventsFile) -> Self { + let val = match file { + EventsFile::Presence => include_str!("../../test_data/events/presence.json"), + _ => panic!("unknown presence event file {:?}", file), + }; + let event = serde_json::from_str::>(&val) .unwrap() .deserialize() @@ -158,3 +202,26 @@ impl EventBuilder { SyncResponse::try_from(response).unwrap() } } + +/// Embedded sync reponse files +pub enum SyncResponseFile { + Default, + DefaultWithSummary, + Invite, + Leave, +} + +/// Get specific API responses for testing +pub fn sync_response(kind: SyncResponseFile) -> SyncResponse { + let data = match kind { + SyncResponseFile::Default => include_bytes!("../../test_data/sync.json").to_vec(), + SyncResponseFile::DefaultWithSummary => { + include_bytes!("../../test_data/sync_with_summary.json").to_vec() + } + SyncResponseFile::Invite => include_bytes!("../../test_data/invite_sync.json").to_vec(), + SyncResponseFile::Leave => include_bytes!("../../test_data/leave_sync.json").to_vec(), + }; + + let response = Response::builder().body(data.to_vec()).unwrap(); + SyncResponse::try_from(response).unwrap() +} diff --git a/matrix_sdk_test_macros/Cargo.toml b/matrix_sdk_test_macros/Cargo.toml new file mode 100644 index 00000000..11a42bdc --- /dev/null +++ b/matrix_sdk_test_macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["stoically "] +description = "Helper macros to write tests for the Matrix SDK" +edition = "2018" +homepage = "https://github.com/matrix-org/matrix-rust-sdk" +keywords = ["matrix", "chat", "messaging", "ruma"] +license = "Apache-2.0" +name = "matrix-sdk-test-macros" +readme = "README.md" +repository = "https://github.com/matrix-org/matrix-rust-sdk" +version = "0.1.0" + +[lib] +proc-macro = true diff --git a/matrix_sdk_test_macros/src/lib.rs b/matrix_sdk_test_macros/src/lib.rs new file mode 100644 index 00000000..3b0ac656 --- /dev/null +++ b/matrix_sdk_test_macros/src/lib.rs @@ -0,0 +1,14 @@ +use proc_macro::TokenStream; + +/// Attribute to use `wasm_bindgen_test` for wasm32 targets and `tokio::test` for everything else +#[proc_macro_attribute] +pub fn async_test(_attr: TokenStream, item: TokenStream) -> TokenStream { + let attrs = r#" + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + "#; + + let mut out: TokenStream = attrs.parse().unwrap(); + out.extend(item); + out +}