diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07f036d2e..36496b63b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,34 @@ jobs: command: run args: -p xtask -- ci test-features ${{ matrix.name }} + test-matrix-sdk-examples: + name: 🐧 [m]-examples + runs-on: ubuntu-latest + if: github.event_name == 'push' || !github.event.pull_request.draft + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci examples + test-matrix-sdk-crypto: name: 🐧 [m]-crypto runs-on: ubuntu-latest diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index b0da847cd..c56b019b2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -39,7 +39,7 @@ jobs: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings" with: command: doc - args: --no-deps --workspace --features docsrs + args: --no-deps --features docsrs - name: Build `matrix-sdk-crypto-nodejs` doc run: | diff --git a/Cargo.toml b/Cargo.toml index bdee9206c..b5e57c990 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "bindings/matrix-sdk-ffi", "crates/*", "testing/*", + "examples/*", "labs/*", "xtask", ] diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index 8e745e90c..afe560f1f 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -36,6 +36,12 @@ use matrix_sdk_base::{ #[cfg(feature = "experimental-timeline")] use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream}; use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher}; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, + CanonicalJsonObject, RoomVersionId, +}; use ruma::{ events::{ presence::PresenceEvent, @@ -47,12 +53,6 @@ use ruma::{ serde::Raw, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, }; -#[cfg(feature = "experimental-timeline")] -use ruma::{ - events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, - signatures::{redact_in_place, CanonicalJsonObject}, - RoomVersionId, -}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[cfg(feature = "experimental-timeline")] use tracing::{info, warn}; @@ -880,7 +880,7 @@ impl IndexeddbStore { let metadata: Option = timeline_metadata_store .get(&self.encode_key(KEYS::ROOM_TIMELINE_METADATA, room_id))? .await? - .map(|v| self.deserialize_event(&v)) + .map(|v| self.deserialize_event(v)) .transpose()?; if let Some(mut metadata) = metadata { if !timeline.sync && Some(&timeline.start) != metadata.end.as_ref() { diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 281c85d7c..df2240674 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -150,15 +150,3 @@ wasm-bindgen-test = "0.3.30" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread", "macros"] } wiremock = "0.5.13" - -[[example]] -name = "cross_signing_bootstrap" -required-features = ["e2e-encryption"] - -[[example]] -name = "emoji_verification" -required-features = ["e2e-encryption"] - -[[example]] -name = "timeline" -required-features = ["sled", "experimental-timeline"] diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index 0fcba9f1f..5633f7dc9 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -29,7 +29,10 @@ //! [`StoreConfig`]: crate::config::StoreConfig //! [`ClientBuilder::store_config()`]: crate::ClientBuilder::store_config -#[cfg(feature = "indexeddb")] +// indexeddb and sled are mutably exclusive and though that is already checked +// before, both using `*`-glob exports confuses the compiler. so we exclude +// them. see https://github.com/rust-lang/rfcs/issues/553#issuecomment-325034618 +#[cfg(all(feature = "indexeddb", not(feature = "sled")))] pub use matrix_sdk_indexeddb::*; #[cfg(feature = "sled")] pub use matrix_sdk_sled::*; diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..3132011e2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Matrix SDK Examples + +In this folder you find examples for using the matrix-sdk (and its various components) to implement specific common features, each as a separate crate. You can run each of them from the root of the repository, mind you that `_` becomes `-` and the crate names are prefixed with `example-`. So to run the image bot example you can do `cargo run -p example-image-bot`. diff --git a/examples/autojoin/Cargo.toml b/examples/autojoin/Cargo.toml new file mode 100644 index 000000000..42aebd728 --- /dev/null +++ b/examples/autojoin/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example-autojoin" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +tokio = { version = "1.20.1", features = ["full"] } +anyhow = "1" +tracing-subscriber = "0.3.15" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/crates/matrix-sdk/examples/autojoin.rs b/examples/autojoin/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/autojoin.rs rename to examples/autojoin/src/main.rs diff --git a/examples/command_bot/Cargo.toml b/examples/command_bot/Cargo.toml new file mode 100644 index 000000000..ac8b169c9 --- /dev/null +++ b/examples/command_bot/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-command-bot" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/crates/matrix-sdk/examples/command_bot.rs b/examples/command_bot/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/command_bot.rs rename to examples/command_bot/src/main.rs diff --git a/examples/cross_signing_bootstrap/Cargo.toml b/examples/cross_signing_bootstrap/Cargo.toml new file mode 100644 index 000000000..bcb9d048a --- /dev/null +++ b/examples/cross_signing_bootstrap/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-cross-signing-bootstrap" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" +version = "0.5.0" +features = ["e2e-encryption"] diff --git a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs b/examples/cross_signing_bootstrap/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/cross_signing_bootstrap.rs rename to examples/cross_signing_bootstrap/src/main.rs diff --git a/examples/custom_events/Cargo.toml b/examples/custom_events/Cargo.toml new file mode 100644 index 000000000..ddec6961a --- /dev/null +++ b/examples/custom_events/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example-custom-events" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +dirs = "4.0.0" +serde = "1.0" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/examples/custom_events/src/main.rs b/examples/custom_events/src/main.rs new file mode 100644 index 000000000..2e8a64851 --- /dev/null +++ b/examples/custom_events/src/main.rs @@ -0,0 +1,184 @@ +/// +/// This is an example showcasing how to build a very simple bot with custom +/// events using the matrix-sdk. To try it, you need a rust build setup, then +/// you can run: `cargo run -p example-custom-events -- +/// ` +/// +/// Use a second client to open a DM to your bot or invite them into some room. +/// You should see it automatically join. Then post `!ping` and observe the log +/// of the bot. You will see that it sends the `Ping` event and upon receiving +/// it responds with the `Ack` event send to the room. You won't see that in +/// most regular clients, unless you activate showing of unknown events. +use std::{env, process::exit}; + +use matrix_sdk::{ + config::SyncSettings, + room::Room, + ruma::{ + events::{ + macros::EventContent, + room::{ + member::StrippedRoomMemberEvent, + message::{MessageType, OriginalSyncRoomMessageEvent, TextMessageEventContent}, + }, + }, + OwnedEventId, + }, + Client, +}; +use serde::{Deserialize, Serialize}; +use tokio::time::{sleep, Duration}; + +// We use ruma to define our custom events. Just declare the events content +// by deriving from `EventContent` and define `ruma_events` for the metadata + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "rs.matrix-sdk.example.ping", kind = MessageLike)] +pub struct PingEventContent {} + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "rs.matrix-sdk.example.ack", kind = MessageLike)] +pub struct AckEventContent { + // the event ID of the ping. + ping_id: OwnedEventId, +} + +// Deriving `EventContent` generates a few types and aliases, +// like wrapping the content into full-blown events: for `PingEventContent` this +// generates us `PingEvent` and `SyncPingEvent`, which have redaction support +// and contain all the other event-metadata like event_id and room_id. We will +// use that for `on_ping_event`. + +// we want to start the ping-ack-flow on "!ping" messages. +async fn on_regular_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { + if let Room::Joined(room) = room { + let msg_body = match event.content.msgtype { + MessageType::Text(TextMessageEventContent { body, .. }) => body, + _ => return, + }; + + if msg_body.contains("!ping") { + let content = PingEventContent {}; + + println!("sending ping"); + room.send(content, None).await.unwrap(); + println!("ping sent"); + } + } +} + +// call this on any PingEvent we receive +async fn on_ping_event(event: SyncPingEvent, room: Room) { + if let Room::Joined(room) = room { + let event_id = event.event_id().to_owned(); + + // Send an ack with the event_id of the ping, as our 'protocol' demands + let content = AckEventContent { ping_id: event_id }; + println!("sending ack"); + room.send(content, None).await.unwrap(); + + println!("ack sent"); + } +} + +// once logged in, this is called where we configure the handlers +// and run the client +async fn sync_loop(client: Client) -> anyhow::Result<()> { + // invite acceptance as in the getting-started-client + client.add_event_handler(on_stripped_state_member).await; + client.sync_once(SyncSettings::default()).await.unwrap(); + + // our customisation: + // - send `PingEvent` on `!ping` in any room + client.add_event_handler(on_regular_room_message).await; + // - send `AckEvent` on `PingEvent` in any room + client.add_event_handler(on_ping_event).await; + + let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + client.sync(settings).await; // this essentially loops until we kill the bot + + Ok(()) +} + +// ------ below is mainly like the getting-started example, see that for docs. + +async fn login_and_sync( + homeserver_url: String, + username: &str, + password: &str, +) -> anyhow::Result<()> { + #[allow(unused_mut)] + let mut client_builder = Client::builder().homeserver_url(homeserver_url); + let home = dirs::data_dir().expect("no home directory found").join("getting_started"); + client_builder = client_builder.sled_store(home, None)?; + let client = client_builder.build().await?; + client + .login_username(username, password) + .initial_device_display_name("getting started bot") + .send() + .await?; + + // it worked! + println!("logged in as {username}"); + sync_loop(client).await +} + +// Whenever we see a new stripped room member event, we've asked our client to +// call this function. So what exactly are we doing then? +async fn on_stripped_state_member( + room_member: StrippedRoomMemberEvent, + client: Client, + room: Room, +) { + if room_member.state_key != client.user_id().unwrap() { + // the invite we've seen isn't for us, but for someone else. ignore + return; + } + + // looks like the room is an invited room, let's attempt to join then + if let Room::Invited(room) = room { + println!("Autojoining room {}", room.room_id()); + let mut delay = 2; + + while let Err(err) = room.accept_invitation().await { + // retry autojoin due to synapse sending invites, before the + // invited user can join for more information see + // https://github.com/matrix-org/synapse/issues/4345 + eprintln!("Failed to join room {} ({err:?}), retrying in {delay}s", room.room_id()); + + sleep(Duration::from_secs(delay)).await; + delay *= 2; + + if delay > 3600 { + eprintln!("Can't join room {} ({err:?})", room.room_id()); + break; + } + } + println!("Successfully joined room {}", room.room_id()); + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // set up some simple stderr logging. You can configure it by changing the env + // var `RUST_LOG` + tracing_subscriber::fmt::init(); + + // parse the command line for homeserver, username and password + let (homeserver_url, username, password) = + match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { + (Some(a), Some(b), Some(c)) => (a, b, c), + _ => { + eprintln!( + "Usage: {} ", + env::args().next().unwrap() + ); + // exist if missing + exit(1) + } + }; + + // our actual runner + login_and_sync(homeserver_url, &username, &password).await?; + Ok(()) +} diff --git a/examples/emoji_verification/Cargo.toml b/examples/emoji_verification/Cargo.toml new file mode 100644 index 000000000..3620379cc --- /dev/null +++ b/examples/emoji_verification/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-emoji-verification" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" +version = "0.5.0" +features = ["e2e-encryption"] diff --git a/crates/matrix-sdk/examples/emoji_verification.rs b/examples/emoji_verification/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/emoji_verification.rs rename to examples/emoji_verification/src/main.rs diff --git a/examples/get_profiles/Cargo.toml b/examples/get_profiles/Cargo.toml new file mode 100644 index 000000000..55338d3e3 --- /dev/null +++ b/examples/get_profiles/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-get-profiles" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/crates/matrix-sdk/examples/get_profiles.rs b/examples/get_profiles/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/get_profiles.rs rename to examples/get_profiles/src/main.rs diff --git a/examples/getting_started/Cargo.toml b/examples/getting_started/Cargo.toml new file mode 100644 index 000000000..4f23dbdf6 --- /dev/null +++ b/examples/getting_started/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-getting-started" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +dirs = "4.0.0" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/examples/getting_started/src/main.rs b/examples/getting_started/src/main.rs new file mode 100644 index 000000000..c31a47be9 --- /dev/null +++ b/examples/getting_started/src/main.rs @@ -0,0 +1,176 @@ +/// +/// This is an example showcasing how to build a very simple bot using the +/// matrix-sdk. To try it, you need a rust build setup, then you can run: +/// `cargo run -p example-getting-started -- ` +/// +/// Use a second client to open a DM to your bot or invite them into some room. +/// You should see it automatically join. Then post `!party` to see the client +/// in action. +/// +/// Below the code has a lot of inline documentation to help you understand the +/// various parts and what they do +// The imports we need +use std::{env, process::exit}; + +use matrix_sdk::{ + config::SyncSettings, + room::Room, + ruma::events::room::{ + member::StrippedRoomMemberEvent, + message::{ + MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, + TextMessageEventContent, + }, + }, + Client, +}; +use tokio::time::{sleep, Duration}; + +/// This is the starting point of the app. `main` is called by rust binaries to +/// run the program in this case, we use tokio (a reactor) to allow us to use +/// an `async` function run. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // set up some simple stderr logging. You can configure it by changing the env + // var `RUST_LOG` + tracing_subscriber::fmt::init(); + + // parse the command line for homeserver, username and password + let (homeserver_url, username, password) = + match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { + (Some(a), Some(b), Some(c)) => (a, b, c), + _ => { + eprintln!( + "Usage: {} ", + env::args().next().unwrap() + ); + // exist if missing + exit(1) + } + }; + + // our actual runner + login_and_sync(homeserver_url, &username, &password).await?; + Ok(()) +} + +// The core sync loop we have running. +async fn login_and_sync( + homeserver_url: String, + username: &str, + password: &str, +) -> anyhow::Result<()> { + // first, we set up the client. We use the convenient client builder to set our + // custom homeserver URL on it + #[allow(unused_mut)] + let mut client_builder = Client::builder().homeserver_url(homeserver_url); + + // Matrix-SDK has support for pluggable, configurable state and crypto-store + // support we use the default sled-store (enabled by default on native + // architectures), to configure a local cache and store for our crypto keys + let home = dirs::data_dir().expect("no home directory found").join("getting_started"); + client_builder = client_builder.sled_store(home, None)?; + + // alright, let's make that into a client + let client = client_builder.build().await?; + + // then let's log that client in + client + .login_username(username, password) + .initial_device_display_name("getting started bot") + .send() + .await?; + + // it worked! + println!("logged in as {username}"); + + // Now, we want our client to react to invites. Invites sent us stripped member + // state events so we want to react to them. We add the event handler before + // the sync, so this happens also for older messages. All rooms we've + // already entered won't have stripped states anymore and thus won't fire + client.add_event_handler(on_stripped_state_member).await; + + // An initial sync to set up state and so our bot doesn't respond to old + // messages. If the `StateStore` finds saved state in the location given the + // initial sync will be skipped in favor of loading state from the store + client.sync_once(SyncSettings::default()).await.unwrap(); + + // now that we've synced, let's attach a handler for incoming room messages, so + // we can react on it + client.add_event_handler(on_room_message).await; + + // since we called `sync_once` before we entered our sync loop we must pass + // that sync token to `sync` + let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + // this keeps state from the server streaming in to the bot via the + // EventHandler trait + client.sync(settings).await; // this essentially loops until we kill the bot + + Ok(()) +} + +// Whenever we see a new stripped room member event, we've asked our client to +// call this function. So what exactly are we doing then? +async fn on_stripped_state_member( + room_member: StrippedRoomMemberEvent, + client: Client, + room: Room, +) { + if room_member.state_key != client.user_id().unwrap() { + // the invite we've seen isn't for us, but for someone else. ignore + return; + } + + // looks like the room is an invited room, let's attempt to join then + if let Room::Invited(room) = room { + println!("Autojoining room {}", room.room_id()); + let mut delay = 2; + + while let Err(err) = room.accept_invitation().await { + // retry autojoin due to synapse sending invites, before the + // invited user can join for more information see + // https://github.com/matrix-org/synapse/issues/4345 + eprintln!("Failed to join room {} ({err:?}), retrying in {delay}s", room.room_id()); + + sleep(Duration::from_secs(delay)).await; + delay *= 2; + + if delay > 3600 { + eprintln!("Can't join room {} ({err:?})", room.room_id()); + break; + } + } + println!("Successfully joined room {}", room.room_id()); + } +} + +// This fn is called whenever we see a new room message event. You notice that +// the difference between this and the other function that we've given to the +// handler lies only in their input parameters. However, that is enough for the +// rust-sdk to figure out which one to call one and only do so, when +// the parameters are available. +async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { + // First, we need to unpack the message: We only want messages from rooms we are + // still in and that are regular text messages - ignoring everything else. + if let Room::Joined(room) = room { + let msg_body = match event.content.msgtype { + MessageType::Text(TextMessageEventContent { body, .. }) => body, + _ => return, + }; + + // here comes the actual "logic": when the bot see's a `!party` in the message, + // it responds + if msg_body.contains("!party") { + let content = RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉"); + + println!("sending"); + + // send our message to the room we found the "!party" command in + // the last parameter is an optional transaction id which we don't + // care about. + room.send(content, None).await.unwrap(); + + println!("message sent"); + } + } +} diff --git a/examples/image_bot/Cargo.toml b/examples/image_bot/Cargo.toml new file mode 100644 index 000000000..699b2e62f --- /dev/null +++ b/examples/image_bot/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example-image-bot" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +mime = "0.3.16" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/crates/matrix-sdk/examples/image_bot.rs b/examples/image_bot/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/image_bot.rs rename to examples/image_bot/src/main.rs diff --git a/examples/login/Cargo.toml b/examples/login/Cargo.toml new file mode 100644 index 000000000..f2b3e16b4 --- /dev/null +++ b/examples/login/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-login" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" diff --git a/crates/matrix-sdk/examples/login.rs b/examples/login/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/login.rs rename to examples/login/src/main.rs diff --git a/examples/timeline/Cargo.toml b/examples/timeline/Cargo.toml new file mode 100644 index 000000000..4e018cebc --- /dev/null +++ b/examples/timeline/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-timeline" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +futures = "0.3" +tokio = { version = "1.20.1", features = ["full"] } +tracing-subscriber = "0.3.15" +url = "2.2.2" + +[dependencies.matrix-sdk] +path = "../../crates/matrix-sdk" +features = ["sled", "experimental-timeline"] diff --git a/crates/matrix-sdk/examples/timeline.rs b/examples/timeline/src/main.rs similarity index 100% rename from crates/matrix-sdk/examples/timeline.rs rename to examples/timeline/src/main.rs diff --git a/crates/matrix-sdk/examples/wasm_command_bot/.gitignore b/examples/wasm_command_bot/.gitignore similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/.gitignore rename to examples/wasm_command_bot/.gitignore diff --git a/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml b/examples/wasm_command_bot/Cargo.toml similarity index 95% rename from crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml rename to examples/wasm_command_bot/Cargo.toml index be89b3c23..e5b190be6 100644 --- a/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml +++ b/examples/wasm_command_bot/Cargo.toml @@ -18,12 +18,10 @@ console_error_panic_hook = "0.1.6" web-sys = { version = "0.3.51", features = ["console"] } [dependencies.matrix-sdk] -path = "../.." +path = "../../crates/matrix-sdk" version = "0.5.0" default-features = false features = ["native-tls", "e2e-encryption", "indexeddb"] -[workspace] - [dev-dependencies] wasm-bindgen-test = "0.3.29" diff --git a/crates/matrix-sdk/examples/wasm_command_bot/README.md b/examples/wasm_command_bot/README.md similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/README.md rename to examples/wasm_command_bot/README.md diff --git a/crates/matrix-sdk/examples/wasm_command_bot/index.js b/examples/wasm_command_bot/index.js similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/index.js rename to examples/wasm_command_bot/index.js diff --git a/crates/matrix-sdk/examples/wasm_command_bot/package.json b/examples/wasm_command_bot/package.json similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/package.json rename to examples/wasm_command_bot/package.json diff --git a/crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs b/examples/wasm_command_bot/src/lib.rs similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs rename to examples/wasm_command_bot/src/lib.rs diff --git a/crates/matrix-sdk/examples/wasm_command_bot/webpack.config.js b/examples/wasm_command_bot/webpack.config.js similarity index 100% rename from crates/matrix-sdk/examples/wasm_command_bot/webpack.config.js rename to examples/wasm_command_bot/webpack.config.js diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index a08352c60..1bed64273 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -44,6 +44,8 @@ enum CiCommand { }, /// Run tests for the different crypto crate features TestCrypto, + /// Check that the examples compile + Examples, } #[derive(Subcommand, PartialEq, Eq, PartialOrd, Ord)] @@ -91,6 +93,7 @@ impl CiArgs { CiCommand::Wasm { cmd } => run_wasm_checks(cmd), CiCommand::WasmPack { cmd } => run_wasm_pack_tests(cmd), CiCommand::TestCrypto => run_crypto_tests(), + CiCommand::Examples => check_examples(), }, None => { check_style()?; @@ -101,6 +104,7 @@ impl CiArgs { run_appservice_tests()?; run_wasm_checks(None)?; run_crypto_tests()?; + check_examples()?; Ok(()) } @@ -108,6 +112,11 @@ impl CiArgs { } } +fn check_examples() -> Result<()> { + cmd!("rustup run stable cargo check -p example-*").run()?; + Ok(()) +} + fn check_style() -> Result<()> { cmd!("rustup run nightly cargo fmt -- --check").run()?; Ok(()) @@ -240,7 +249,7 @@ fn run_wasm_checks(cmd: Option) -> Result<()> { }; let test_command_bot = || { - let _p = pushd("crates/matrix-sdk/examples/wasm_command_bot"); + let _p = pushd("examples/wasm_command_bot"); cmd!("rustup run stable cargo clippy --target wasm32-unknown-unknown") .args(["--", "-D", "warnings", "-A", "clippy::unused-unit"]) @@ -316,7 +325,7 @@ fn run_wasm_pack_tests(cmd: Option) -> Result<()> { }; let test_command_bot = || { - let _p = pushd("crates/matrix-sdk/examples/wasm_command_bot"); + let _p = pushd("examples/wasm_command_bot"); cmd!("wasm-pack test --node").env(WASM_TIMEOUT_ENV_KEY, WASM_TIMEOUT_VALUE).run()?; cmd!("wasm-pack test --firefox --headless") .env(WASM_TIMEOUT_ENV_KEY, WASM_TIMEOUT_VALUE)