diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs
index 35ec906b855..682dca6ce37 100644
--- a/crates/matrix-sdk-base/src/client.rs
+++ b/crates/matrix-sdk-base/src/client.rs
@@ -22,9 +22,7 @@ use std::{
 };
 
 use eyeball::{SharedObservable, Subscriber};
-#[cfg(not(target_arch = "wasm32"))]
 use eyeball_im::{Vector, VectorDiff};
-#[cfg(not(target_arch = "wasm32"))]
 use futures_util::Stream;
 #[cfg(feature = "e2e-encryption")]
 use matrix_sdk_crypto::{
@@ -228,7 +226,6 @@ impl BaseClient {
 
     /// Get a stream of all the rooms changes, in addition to the existing
     /// rooms.
-    #[cfg(not(target_arch = "wasm32"))]
     pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>>) {
         self.store.rooms_stream()
     }
diff --git a/crates/matrix-sdk-base/src/event_cache_store/traits.rs b/crates/matrix-sdk-base/src/event_cache_store/traits.rs
index 6c56166177b..f7cb15ec492 100644
--- a/crates/matrix-sdk-base/src/event_cache_store/traits.rs
+++ b/crates/matrix-sdk-base/src/event_cache_store/traits.rs
@@ -15,7 +15,6 @@
 use std::{fmt, sync::Arc};
 
 use async_trait::async_trait;
-use matrix_sdk_common::AsyncTraitDeps;
 use ruma::MxcUri;
 
 use super::EventCacheStoreError;
@@ -25,7 +24,7 @@ use crate::media::MediaRequest;
 /// for the event cache of the SDK.
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-pub trait EventCacheStore: AsyncTraitDeps {
+pub trait EventCacheStore: Send + Sync {
     /// The error type used by this event cache store.
     type Error: fmt::Debug + Into<EventCacheStoreError>;
 
diff --git a/crates/matrix-sdk-base/src/read_receipts.rs b/crates/matrix-sdk-base/src/read_receipts.rs
index f582bdd5ade..7fd14721b63 100644
--- a/crates/matrix-sdk-base/src/read_receipts.rs
+++ b/crates/matrix-sdk-base/src/read_receipts.rs
@@ -266,12 +266,21 @@ impl RoomReadReceipts {
 }
 
 /// Provider for timeline events prior to the current sync.
+#[cfg(not(target_arch = "wasm32"))]
 pub trait PreviousEventsProvider: Send + Sync {
     /// Returns the list of known timeline events, in sync order, for the given
     /// room.
     fn for_room(&self, room_id: &RoomId) -> Vector<SyncTimelineEvent>;
 }
 
+/// Provider for timeline events prior to the current sync.
+#[cfg(target_arch = "wasm32")]
+pub trait PreviousEventsProvider {
+    /// Returns the list of known timeline events, in sync order, for the given
+    /// room.
+    fn for_room(&self, room_id: &RoomId) -> Vector<SyncTimelineEvent>;
+}
+
 impl PreviousEventsProvider for () {
     fn for_room(&self, _: &RoomId) -> Vector<SyncTimelineEvent> {
         Vector::new()
diff --git a/crates/matrix-sdk-base/src/store/memory_store.rs b/crates/matrix-sdk-base/src/store/memory_store.rs
index 995c7113870..babfc885473 100644
--- a/crates/matrix-sdk-base/src/store/memory_store.rs
+++ b/crates/matrix-sdk-base/src/store/memory_store.rs
@@ -137,8 +137,7 @@ impl MemoryStore {
     }
 }
 
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[async_trait]
 impl StateStore for MemoryStore {
     type Error = StoreError;
 
diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs
index 9c774a769a0..dacfe87c989 100644
--- a/crates/matrix-sdk-base/src/store/mod.rs
+++ b/crates/matrix-sdk-base/src/store/mod.rs
@@ -29,9 +29,7 @@ use std::{
     sync::{Arc, RwLock as StdRwLock},
 };
 
-#[cfg(not(target_arch = "wasm32"))]
 use eyeball_im::{Vector, VectorDiff};
-#[cfg(not(target_arch = "wasm32"))]
 use futures_util::Stream;
 use once_cell::sync::OnceCell;
 
@@ -60,9 +58,9 @@ use tokio::sync::{broadcast, Mutex, RwLock};
 use tracing::warn;
 
 use crate::{
-    event_cache_store::{DynEventCacheStore, IntoEventCacheStore},
-    rooms::{normal::RoomInfoNotableUpdate, RoomInfo, RoomState},
-    MinimalRoomMemberEvent, Room, RoomStateFilter, SessionMeta,
+    event_cache_store::{DynEventCacheStore, IntoEventCacheStore}, 
+    rooms::{normal::RoomInfoNotableUpdate, RoomInfo, RoomState}, 
+    MinimalRoomMemberEvent, Room, RoomStateFilter, SessionMeta
 };
 
 pub(crate) mod ambiguity_map;
@@ -263,7 +261,6 @@ impl Store {
 
     /// Get a stream of all the rooms changes, in addition to the existing
     /// rooms.
-    #[cfg(not(target_arch = "wasm32"))]
     pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>>) {
         self.rooms.read().unwrap().stream()
     }
diff --git a/crates/matrix-sdk-base/src/store/observable_map.rs b/crates/matrix-sdk-base/src/store/observable_map.rs
index e15124a9520..00d85c52580 100644
--- a/crates/matrix-sdk-base/src/store/observable_map.rs
+++ b/crates/matrix-sdk-base/src/store/observable_map.rs
@@ -136,6 +136,9 @@ mod impl_non_wasm32 {
 #[cfg(target_arch = "wasm32")]
 mod impl_wasm32 {
     use std::{borrow::Borrow, collections::BTreeMap, hash::Hash};
+    use futures_util::stream;
+    use futures_util::{Stream, StreamExt};
+    use eyeball_im::{Vector, VectorDiff};
 
     /// An observable map for Wasm. It's a simple wrapper around `BTreeMap`.
     #[derive(Debug)]
@@ -184,6 +187,13 @@ mod impl_wasm32 {
         pub(crate) fn iter(&self) -> impl Iterator<Item = &V> {
             self.0.values()
         }
+
+        /// Get a [`Stream`] of the values.
+        pub(crate) fn stream(&self) -> (Vector<V>, impl Stream<Item = Vec<VectorDiff<V>>>) {
+            let values: Vector<V> = self.0.values().cloned().collect();
+            let stream = stream::iter(vec![values.clone()]).map(|v| vec![VectorDiff::Reset{ values: v }]);
+            (values, stream)
+        }
     }
 }
 
diff --git a/crates/matrix-sdk-base/src/store/traits.rs b/crates/matrix-sdk-base/src/store/traits.rs
index d96e3f24cb0..cd7cceb2413 100644
--- a/crates/matrix-sdk-base/src/store/traits.rs
+++ b/crates/matrix-sdk-base/src/store/traits.rs
@@ -23,7 +23,6 @@ use std::{
 use as_variant::as_variant;
 use async_trait::async_trait;
 use growable_bloom_filter::GrowableBloom;
-use matrix_sdk_common::AsyncTraitDeps;
 use ruma::{
     api::MatrixVersion,
     events::{
@@ -50,9 +49,8 @@ use crate::{
 
 /// An abstract state store trait that can be used to implement different stores
 /// for the SDK.
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-pub trait StateStore: AsyncTraitDeps {
+#[async_trait]
+pub trait StateStore: fmt::Debug + Send + Sync {
     /// The error type used by this state store.
     type Error: fmt::Debug + Into<StoreError> + From<serde_json::Error>;
 
@@ -452,8 +450,7 @@ impl<T: fmt::Debug> fmt::Debug for EraseStateStoreError<T> {
     }
 }
 
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[async_trait]
 impl<T: StateStore> StateStore for EraseStateStoreError<T> {
     type Error = StoreError;
 
diff --git a/crates/matrix-sdk-common/src/executor.rs b/crates/matrix-sdk-common/src/executor.rs
index b1cb1a7bf13..7f065fc72f5 100644
--- a/crates/matrix-sdk-common/src/executor.rs
+++ b/crates/matrix-sdk-common/src/executor.rs
@@ -14,24 +14,28 @@
 
 //! Abstraction over an executor so we can spawn tasks under WASM the same way
 //! we do usually.
-
-#[cfg(target_arch = "wasm32")]
 use std::{
     future::Future,
     pin::Pin,
     task::{Context, Poll},
 };
 
+use futures_util::FutureExt;
 #[cfg(target_arch = "wasm32")]
 pub use futures_util::future::Aborted as JoinError;
 #[cfg(target_arch = "wasm32")]
-use futures_util::{
-    future::{AbortHandle, Abortable, RemoteHandle},
-    FutureExt,
-};
+use futures_util::future::{AbortHandle, Abortable, RemoteHandle};
 #[cfg(not(target_arch = "wasm32"))]
 pub use tokio::task::{spawn, JoinError, JoinHandle};
 
+
+/// A `Box::pin` future that is `Send` on non-wasm, and without `Send` on wasm.
+#[cfg(target_arch = "wasm32")]
+pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
+#[cfg(not(target_arch = "wasm32"))]
+pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
+
+
 #[cfg(target_arch = "wasm32")]
 pub fn spawn<F, T>(future: F) -> JoinHandle<T>
 where
@@ -50,6 +54,32 @@ where
     JoinHandle { remote_handle, abort_handle }
 }
 
+pub trait BoxFutureExt<'a, T: 'a> {
+    fn box_future(self) -> BoxFuture<'a, T>;
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl<'a, F, T> BoxFutureExt<'a, T> for F
+where
+    F: Future<Output = T> + 'a + Send,
+    T: 'a,
+{
+    fn box_future(self) -> BoxFuture<'a, T> {
+        self.boxed()
+    }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl<'a, F, T> BoxFutureExt<'a, T> for F
+where
+    F: Future<Output = T> + 'a,
+    T: 'a,
+{
+    fn box_future(self) -> BoxFuture<'a, T> {
+        self.boxed_local()
+    }
+}
+
 #[cfg(target_arch = "wasm32")]
 #[derive(Debug)]
 pub struct JoinHandle<T> {
@@ -78,6 +108,54 @@ impl<T: 'static> Future for JoinHandle<T> {
     }
 }
 
+#[derive(Debug)]
+pub struct AbortOnDrop<T>(JoinHandle<T>);
+
+impl<T> AbortOnDrop<T> {
+    pub fn new(join_handle: JoinHandle<T>) -> Self {
+        Self(join_handle)
+    }
+}
+
+impl<T> Drop for AbortOnDrop<T> {
+    fn drop(&mut self) {
+        self.0.abort();
+    }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl<T> Future for AbortOnDrop<T> {
+    type Output = Result<T, JoinError>;
+
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        Pin::new(&mut self.0).poll(cx)
+    }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl<T: 'static> Future for AbortOnDrop<T> {
+    type Output = Result<T, JoinError>;
+
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        if self.0.abort_handle.is_aborted() {
+            // The future has been aborted. It is not possible to poll it again.
+            Poll::Ready(Err(JoinError))
+        } else {
+            Pin::new(&mut self.0.remote_handle).poll(cx).map(Ok)
+        }
+    }
+}
+
+/// Trait to create a `AbortOnDrop` from a `JoinHandle`.
+pub trait JoinHandleExt<T> {
+    fn abort_on_drop(self) -> AbortOnDrop<T>;
+}
+
+impl<T> JoinHandleExt<T> for JoinHandle<T> {
+    fn abort_on_drop(self) -> AbortOnDrop<T> {
+        AbortOnDrop::new(self)
+    }
+}
 #[cfg(test)]
 mod tests {
     use assert_matches::assert_matches;
diff --git a/crates/matrix-sdk-common/src/lib.rs b/crates/matrix-sdk-common/src/lib.rs
index 451a86fa16c..851dc16b576 100644
--- a/crates/matrix-sdk-common/src/lib.rs
+++ b/crates/matrix-sdk-common/src/lib.rs
@@ -15,9 +15,6 @@
 #![doc = include_str!("../README.md")]
 #![warn(missing_debug_implementations)]
 
-use std::pin::Pin;
-
-use futures_core::Future;
 #[doc(no_inline)]
 pub use ruma;
 
@@ -90,11 +87,5 @@ macro_rules! boxed_into_future {
     };
 }
 
-/// A `Box::pin` future that is `Send` on non-wasm, and without `Send` on wasm.
-#[cfg(target_arch = "wasm32")]
-pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
-#[cfg(not(target_arch = "wasm32"))]
-pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
-
 #[cfg(feature = "uniffi")]
 uniffi::setup_scaffolding!();
diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs
index 80b2c1d44d5..99baeece542 100644
--- a/crates/matrix-sdk-crypto/src/machine/mod.rs
+++ b/crates/matrix-sdk-crypto/src/machine/mod.rs
@@ -24,7 +24,7 @@ use matrix_sdk_common::{
         AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, UnableToDecryptInfo,
         UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState,
     },
-    BoxFuture,
+    executor::BoxFuture,
 };
 use ruma::{
     api::client::{
diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs
index b773d819d94..c0c60456671 100644
--- a/crates/matrix-sdk-crypto/src/store/memorystore.rs
+++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs
@@ -194,8 +194,7 @@ impl MemoryStore {
 
 type Result<T> = std::result::Result<T, Infallible>;
 
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[async_trait]
 impl CryptoStore for MemoryStore {
     type Error = Infallible;
 
diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs
index 3031e33ef8d..bc2b33e48fd 100644
--- a/crates/matrix-sdk-crypto/src/store/traits.rs
+++ b/crates/matrix-sdk-crypto/src/store/traits.rs
@@ -15,7 +15,6 @@
 use std::{collections::HashMap, fmt, sync::Arc};
 
 use async_trait::async_trait;
-use matrix_sdk_common::AsyncTraitDeps;
 use ruma::{
     events::secret::request::SecretName, DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId,
 };
@@ -37,9 +36,8 @@ use crate::{
 
 /// Represents a store that the `OlmMachine` uses to store E2EE data (such as
 /// cryptographic keys).
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-pub trait CryptoStore: AsyncTraitDeps {
+#[async_trait]
+pub trait CryptoStore: fmt::Debug + Send + Sync {
     /// The error type used by this crypto store.
     type Error: fmt::Debug + Into<CryptoStoreError>;
 
@@ -369,8 +367,7 @@ impl<T: fmt::Debug> fmt::Debug for EraseCryptoStoreError<T> {
     }
 }
 
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[async_trait]
 impl<T: CryptoStore> CryptoStore for EraseCryptoStoreError<T> {
     type Error = CryptoStoreError;
 
diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs
index 2eb01063da0..1085400f4e9 100644
--- a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs
+++ b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs
@@ -102,7 +102,10 @@ pub trait Filter: Fn(&Room) -> bool {}
 impl<F> Filter for F where F: Fn(&Room) -> bool {}
 
 /// Type alias for a boxed filter function.
+#[cfg(not(target_arch = "wasm32"))]
 pub type BoxedFilterFn = Box<dyn Filter + Send + Sync>;
+#[cfg(target_arch = "wasm32")]
+pub type BoxedFilterFn = Box<dyn Filter>;
 
 /// Normalize a string, i.e. decompose it into NFD (Normalization Form D, i.e. a
 /// canonical decomposition, see http://www.unicode.org/reports/tr15/) and
diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs
index 23c8166cafe..f914df253cb 100644
--- a/crates/matrix-sdk-ui/src/sync_service.rs
+++ b/crates/matrix-sdk-ui/src/sync_service.rs
@@ -30,12 +30,10 @@ use futures_core::Future;
 use futures_util::{pin_mut, StreamExt as _};
 use matrix_sdk::Client;
 use thiserror::Error;
-use tokio::{
-    sync::{
-        mpsc::{Receiver, Sender},
-        Mutex as AsyncMutex, OwnedMutexGuard,
-    },
-    task::{spawn, JoinHandle},
+use matrix_sdk_base::executor::{spawn, JoinHandle};
+use tokio::sync::{
+    mpsc::{Receiver, Sender},
+    Mutex as AsyncMutex, OwnedMutexGuard,
 };
 use tracing::{error, info, instrument, trace, warn, Instrument, Level};
 
diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/local.rs b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs
index 2890b6eab8e..8846c2f4d89 100644
--- a/crates/matrix-sdk-ui/src/timeline/event_item/local.rs
+++ b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs
@@ -28,7 +28,7 @@ pub(in crate::timeline) struct LocalEventTimelineItem {
     pub send_state: EventSendState,
     /// The transaction ID.
     pub transaction_id: OwnedTransactionId,
-    /// A handle to manipulate this event before it is sent, if possible.
+    // A handle to manipulate this event before it is sent, if possible.
     pub send_handle: Option<SendHandle>,
 }
 
diff --git a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs
index 450ccae0e94..7c7f4f90297 100644
--- a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs
+++ b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs
@@ -14,10 +14,9 @@
 
 use std::{fmt::Formatter, sync::Arc};
 
-use futures_util::{stream, FutureExt as _, StreamExt};
+use futures_util::{stream, StreamExt};
 use matrix_sdk::{
-    config::RequestConfig, event_cache::paginator::PaginatorError, BoxFuture, Room,
-    SendOutsideWasm, SyncOutsideWasm,
+    config::RequestConfig, event_cache::paginator::PaginatorError, executor::{BoxFuture, BoxFutureExt}, Room,
 };
 use matrix_sdk_base::deserialized_responses::SyncTimelineEvent;
 use ruma::{events::relation::RelationType, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId};
@@ -119,7 +118,7 @@ impl PinnedEventsLoader {
     }
 }
 
-pub trait PinnedEventsRoom: SendOutsideWasm + SyncOutsideWasm {
+pub trait PinnedEventsRoom: Send + Sync {
     /// Load a single room event using the cache or network and any events
     /// related to it, if they are cached.
     ///
@@ -165,7 +164,7 @@ impl PinnedEventsRoom for Room {
                 .map(|e| (e.into(), Vec::new()))
                 .map_err(|err| PaginatorError::SdkError(Box::new(err)))
         }
-        .boxed()
+        .box_future()
     }
 
     fn pinned_event_ids(&self) -> Vec<OwnedEventId> {
diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs
index 5c12916221f..fba8e68c6ee 100644
--- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs
+++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs
@@ -27,12 +27,12 @@ use futures_util::FutureExt as _;
 use indexmap::IndexMap;
 use matrix_sdk::{
     config::RequestConfig,
-    deserialized_responses::{SyncTimelineEvent, TimelineEvent},
-    event_cache::paginator::{PaginableRoom, PaginatorError},
-    room::{EventWithContextResponse, Messages, MessagesOptions},
-    send_queue::RoomSendQueueUpdate,
+    deserialized_responses::{SyncTimelineEvent, TimelineEvent}, 
+    event_cache::paginator::{PaginableRoom, PaginatorError}, 
+    executor::{BoxFuture, BoxFutureExt}, 
+    room::{EventWithContextResponse, Messages, MessagesOptions}, 
+    send_queue::RoomSendQueueUpdate, 
     test_utils::events::EventFactory,
-    BoxFuture,
 };
 use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo, RoomState};
 use matrix_sdk_test::{EventBuilder, ALICE, BOB, DEFAULT_TEST_ROOM_ID};
@@ -369,7 +369,7 @@ impl RoomDataProvider for TestRoomDataProvider {
     }
 
     fn profile_from_user_id<'a>(&'a self, _user_id: &'a UserId) -> BoxFuture<'a, Option<Profile>> {
-        ready(None).boxed()
+        ready(None).box_future()
     }
 
     fn profile_from_latest_event(&self, _latest_event: &LatestEvent) -> Option<Profile> {
@@ -389,7 +389,7 @@ impl RoomDataProvider for TestRoomDataProvider {
                 .and_then(|user_map| user_map.get(user_id))
                 .cloned(),
         )
-        .boxed()
+        .box_future()
     }
 
     fn load_event_receipts(
@@ -401,7 +401,7 @@ impl RoomDataProvider for TestRoomDataProvider {
         } else {
             IndexMap::new()
         })
-        .boxed()
+        .box_future()
     }
 
     fn push_rules_and_context(&self) -> BoxFuture<'_, Option<(Ruleset, PushConditionRoomCtx)>> {
@@ -419,11 +419,11 @@ impl RoomDataProvider for TestRoomDataProvider {
             power_levels: Some(power_levels),
         };
 
-        ready(Some((push_rules, push_context))).boxed()
+        ready(Some((push_rules, push_context))).box_future()
     }
 
     fn load_fully_read_marker(&self) -> BoxFuture<'_, Option<OwnedEventId>> {
-        ready(self.fully_read_marker.clone()).boxed()
+        ready(self.fully_read_marker.clone()).box_future()
     }
 
     fn send(&self, content: AnyMessageLikeEventContent) -> BoxFuture<'_, Result<(), super::Error>> {
@@ -431,7 +431,7 @@ impl RoomDataProvider for TestRoomDataProvider {
             self.sent_events.write().await.push(content);
             Ok(())
         }
-        .boxed()
+        .box_future()
     }
 
     fn redact<'a>(
@@ -444,7 +444,7 @@ impl RoomDataProvider for TestRoomDataProvider {
             self.redacted.write().await.push(event_id.to_owned());
             Ok(())
         }
-        .boxed()
+        .box_future()
     }
 
     fn room_info(&self) -> Subscriber<RoomInfo> {
diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs
index 158488eb06f..47a2f676a7c 100644
--- a/crates/matrix-sdk-ui/src/timeline/traits.rs
+++ b/crates/matrix-sdk-ui/src/timeline/traits.rs
@@ -15,13 +15,14 @@
 use std::future::Future;
 
 use eyeball::Subscriber;
-use futures_util::FutureExt as _;
 use indexmap::IndexMap;
 #[cfg(test)]
 use matrix_sdk::crypto::{DecryptionSettings, TrustRequirement};
 use matrix_sdk::{
-    deserialized_responses::TimelineEvent, event_cache::paginator::PaginableRoom, BoxFuture,
-    Result, Room,
+    deserialized_responses::TimelineEvent, 
+    event_cache::paginator::PaginableRoom, 
+    executor::{BoxFuture, BoxFutureExt as _}, 
+    AsyncTraitDeps, Result, Room
 };
 use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo};
 use ruma::{
@@ -47,7 +48,18 @@ pub trait RoomExt {
     /// independent events.
     ///
     /// This is the same as using `room.timeline_builder().build()`.
-    fn timeline(&self) -> impl Future<Output = Result<Timeline, timeline::Error>> + Send;
+    #[cfg(not(target_arch = "wasm32"))]
+    fn timeline(&self) -> impl  Future<Output = Result<Timeline, timeline::Error>> + Send;
+    
+    /// Get a [`Timeline`] for this room.
+    ///
+    /// This offers a higher-level API than event handlers, in treating things
+    /// like edits and reactions as updates of existing items rather than new
+    /// independent events.
+    ///
+    /// This is the same as using `room.timeline_builder().build()`.
+    #[cfg(target_arch = "wasm32")]
+    fn timeline(&self) -> impl  Future<Output = Result<Timeline, timeline::Error>>;
 
     /// Get a [`TimelineBuilder`] for this room.
     ///
@@ -70,8 +82,7 @@ impl RoomExt for Room {
     }
 }
 
-pub(super) trait RoomDataProvider:
-    Clone + Send + Sync + 'static + PaginableRoom + PinnedEventsRoom
+pub(super) trait RoomDataProvider: AsyncTraitDeps + Clone + 'static + PaginableRoom + PinnedEventsRoom
 {
     fn own_user_id(&self) -> &UserId;
     fn room_version(&self) -> RoomVersionId;
@@ -137,7 +148,7 @@ impl RoomDataProvider for Room {
                 }
             }
         }
-        .boxed()
+        .box_future()
     }
 
     fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile> {
@@ -172,7 +183,7 @@ impl RoomDataProvider for Room {
                 }
             }
         }
-        .boxed()
+        .box_future()
     }
 
     fn load_event_receipts<'a>(
@@ -205,7 +216,7 @@ impl RoomDataProvider for Room {
             unthreaded_receipts.extend(main_thread_receipts);
             unthreaded_receipts
         }
-        .boxed()
+        .box_future()
     }
 
     fn push_rules_and_context(&self) -> BoxFuture<'_, Option<(Ruleset, PushConditionRoomCtx)>> {
@@ -228,7 +239,7 @@ impl RoomDataProvider for Room {
                 }
             }
         }
-        .boxed()
+        .box_future()
     }
 
     fn load_fully_read_marker(&self) -> BoxFuture<'_, Option<OwnedEventId>> {
@@ -248,7 +259,7 @@ impl RoomDataProvider for Room {
                 _ => None,
             }
         }
-        .boxed()
+        .box_future()
     }
 
     fn send(&self, content: AnyMessageLikeEventContent) -> BoxFuture<'_, Result<(), super::Error>> {
@@ -256,7 +267,7 @@ impl RoomDataProvider for Room {
             let _ = self.send_queue().send(content).await?;
             Ok(())
         }
-        .boxed()
+        .box_future()
     }
 
     fn redact<'a>(
@@ -273,7 +284,7 @@ impl RoomDataProvider for Room {
                 .map_err(super::Error::RedactError)?;
             Ok(())
         }
-        .boxed()
+        .box_future()
     }
 
     fn room_info(&self) -> Subscriber<RoomInfo> {
@@ -283,11 +294,19 @@ impl RoomDataProvider for Room {
 
 // Internal helper to make most of retry_event_decryption independent of a room
 // object, which is annoying to create for testing and not really needed
-pub(super) trait Decryptor: Clone + Send + Sync + 'static {
+pub(super) trait Decryptor: Clone + AsyncTraitDeps + 'static {
+
+    #[cfg(not(target_arch = "wasm32"))]
     fn decrypt_event_impl(
         &self,
         raw: &Raw<AnySyncTimelineEvent>,
     ) -> impl Future<Output = Result<TimelineEvent>> + Send;
+
+    #[cfg(target_arch = "wasm32")]
+    fn decrypt_event_impl(
+        &self,
+        raw: &Raw<AnySyncTimelineEvent>,
+    ) -> impl Future<Output = Result<TimelineEvent>>;
 }
 
 impl Decryptor for Room {
diff --git a/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs b/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs
index 14f72aee824..a0755c690e1 100644
--- a/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs
+++ b/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs
@@ -26,12 +26,11 @@ use std::{
 
 use growable_bloom_filter::{GrowableBloom, GrowableBloomBuilder};
 use matrix_sdk::{crypto::types::events::UtdCause, Client};
+use matrix_sdk::executor::{spawn, JoinHandle};
 use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue, StoreError};
 use ruma::{EventId, OwnedEventId};
 use tokio::{
-    spawn,
     sync::{Mutex as AsyncMutex, MutexGuard},
-    task::JoinHandle,
     time::sleep,
 };
 use tracing::error;
diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml
index 2b2a5cd7062..415e8a45ef3 100644
--- a/crates/matrix-sdk/Cargo.toml
+++ b/crates/matrix-sdk/Cargo.toml
@@ -16,7 +16,7 @@ features = ["docsrs"]
 rustdoc-args = ["--cfg", "docsrs"]
 
 [features]
-default = ["e2e-encryption", "automatic-room-key-forwarding", "sqlite", "native-tls"]
+default = ["e2e-encryption", "automatic-room-key-forwarding", "sqlite", "native-tls", "js"]
 testing = ["matrix-sdk-sqlite?/testing", "matrix-sdk-indexeddb?/testing", "matrix-sdk-base/testing", "wiremock", "matrix-sdk-test", "assert_matches2"]
 
 e2e-encryption = [
diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs
index 92619b03089..d315d2a550e 100644
--- a/crates/matrix-sdk/src/client/mod.rs
+++ b/crates/matrix-sdk/src/client/mod.rs
@@ -23,12 +23,8 @@ use std::{
 };
 
 use eyeball::{SharedObservable, Subscriber};
-#[cfg(not(target_arch = "wasm32"))]
 use eyeball_im::VectorDiff;
-use futures_core::Stream;
-#[cfg(not(target_arch = "wasm32"))]
-use futures_util::StreamExt;
-#[cfg(not(target_arch = "wasm32"))]
+use futures_util::{Stream, StreamExt};
 use imbl::Vector;
 #[cfg(feature = "e2e-encryption")]
 use matrix_sdk_base::crypto::store::LockableCryptoStore;
@@ -37,7 +33,7 @@ use matrix_sdk_base::{
     store::{DynStateStore, ServerCapabilities},
     sync::{Notification, RoomUpdates},
     BaseClient, RoomInfoNotableUpdate, RoomState, RoomStateFilter, SendOutsideWasm, SessionMeta,
-    StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm,
+    StateStoreDataKey, StateStoreDataValue,
 };
 #[cfg(feature = "e2e-encryption")]
 use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
@@ -114,11 +110,8 @@ type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
 #[cfg(target_arch = "wasm32")]
 type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
 
-#[cfg(not(target_arch = "wasm32"))]
 type NotificationHandlerFn =
     Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
-#[cfg(target_arch = "wasm32")]
-type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
 
 /// Enum controlling if a loop running callbacks should continue or abort.
 ///
@@ -894,7 +887,7 @@ impl Client {
     /// [`Client`] for now.
     pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
     where
-        H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
+        H: Fn(Notification, Room, Client) -> Fut + Send + Sync + 'static,
         Fut: Future<Output = ()> + SendOutsideWasm + 'static,
     {
         self.inner.notification_handlers.write().await.push(Box::new(
@@ -948,7 +941,6 @@ impl Client {
     }
 
     /// Get a stream of all the rooms, in addition to the existing rooms.
-    #[cfg(not(target_arch = "wasm32"))]
     pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
         let (rooms, stream) = self.base_client().rooms_stream();
 
diff --git a/crates/matrix-sdk/src/event_handler/mod.rs b/crates/matrix-sdk/src/event_handler/mod.rs
index c375619811d..1a0022aa6d2 100644
--- a/crates/matrix-sdk/src/event_handler/mod.rs
+++ b/crates/matrix-sdk/src/event_handler/mod.rs
@@ -39,8 +39,7 @@ use std::{
     future::Future,
     pin::Pin,
     sync::{
-        atomic::{AtomicU64, Ordering::SeqCst},
-        RwLock,
+        atomic::{AtomicU64, Ordering::SeqCst}, RwLock
     },
 };
 
@@ -48,7 +47,7 @@ use anymap2::any::CloneAnySendSync;
 use futures_util::stream::{FuturesUnordered, StreamExt};
 use matrix_sdk_base::{
     deserialized_responses::{EncryptionInfo, SyncTimelineEvent},
-    SendOutsideWasm, SyncOutsideWasm,
+    SendOutsideWasm,
 };
 use ruma::{events::AnySyncStateEvent, push::Action, serde::Raw, OwnedRoomId};
 use serde::{de::DeserializeOwned, Deserialize};
@@ -69,10 +68,7 @@ type EventHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
 #[cfg(target_arch = "wasm32")]
 type EventHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
 
-#[cfg(not(target_arch = "wasm32"))]
 type EventHandlerFn = dyn Fn(EventHandlerData<'_>) -> EventHandlerFut + Send + Sync;
-#[cfg(target_arch = "wasm32")]
-type EventHandlerFn = dyn Fn(EventHandlerData<'_>) -> EventHandlerFut;
 
 type AnyMap = anymap2::Map<dyn CloneAnySendSync + Send + Sync>;
 
@@ -197,7 +193,7 @@ pub struct EventHandlerHandle {
 ///
 /// ¹ the only thing stopping such types from existing in stable Rust is that
 /// all manual implementations of the `Fn` traits require a Nightly feature
-pub trait EventHandler<Ev, Ctx>: Clone + SendOutsideWasm + SyncOutsideWasm + 'static {
+pub trait EventHandler<Ev, Ctx>: Clone + Send + Sync + 'static {
     /// The future returned by `handle_event`.
     #[doc(hidden)]
     type Future: EventHandlerFuture;
@@ -512,7 +508,7 @@ macro_rules! impl_event_handler {
         impl<Ev, Fun, Fut, $($ty),*> EventHandler<Ev, ($($ty,)*)> for Fun
         where
             Ev: SyncEvent,
-            Fun: FnOnce(Ev, $($ty),*) -> Fut + Clone + SendOutsideWasm + SyncOutsideWasm + 'static,
+            Fun: FnOnce(Ev, $($ty),*) -> Fut + Clone + Send + Sync + 'static,
             Fut: EventHandlerFuture,
             $($ty: EventHandlerContext),*
         {
diff --git a/crates/matrix-sdk/src/room/futures.rs b/crates/matrix-sdk/src/room/futures.rs
index ee8d278a281..5c2063914f4 100644
--- a/crates/matrix-sdk/src/room/futures.rs
+++ b/crates/matrix-sdk/src/room/futures.rs
@@ -275,7 +275,6 @@ impl<'a> SendAttachment<'a> {
 
     /// Replace the default `SharedObservable` used for tracking upload
     /// progress.
-    #[cfg(not(target_arch = "wasm32"))]
     pub fn with_send_progress_observable(
         mut self,
         send_progress: SharedObservable<TransmissionProgress>,
diff --git a/crates/matrix-sdk/src/sliding_sync/error.rs b/crates/matrix-sdk/src/sliding_sync/error.rs
index c72d5333e03..5b06a120672 100644
--- a/crates/matrix-sdk/src/sliding_sync/error.rs
+++ b/crates/matrix-sdk/src/sliding_sync/error.rs
@@ -1,7 +1,7 @@
 //! Sliding Sync errors.
 
 use thiserror::Error;
-use tokio::task::JoinError;
+use matrix_sdk_common::executor::JoinError;
 
 /// Internal representation of errors in Sliding Sync.
 #[derive(Error, Debug)]
diff --git a/crates/matrix-sdk/src/sliding_sync/mod.rs b/crates/matrix-sdk/src/sliding_sync/mod.rs
index 9de9a21afb7..3793bfe1f9d 100644
--- a/crates/matrix-sdk/src/sliding_sync/mod.rs
+++ b/crates/matrix-sdk/src/sliding_sync/mod.rs
@@ -22,7 +22,6 @@ mod error;
 mod list;
 mod room;
 mod sticky_parameters;
-mod utils;
 
 use std::{
     collections::{btree_map::Entry, BTreeMap, HashSet},
@@ -36,20 +35,21 @@ use async_stream::stream;
 pub use client::{Version, VersionBuilder};
 use futures_core::stream::Stream;
 pub use matrix_sdk_base::sliding_sync::http;
-use matrix_sdk_common::timer;
+use matrix_sdk_common::{executor::spawn, timer};
+#[cfg(feature = "e2e-encryption")]
+use matrix_sdk_common::executor::JoinHandleExt as _;
+
 use ruma::{
     api::{client::error::ErrorKind, OutgoingRequest},
     assign, OwnedEventId, OwnedRoomId, RoomId,
 };
 use serde::{Deserialize, Serialize};
 use tokio::{
-    select, spawn,
+    select,
     sync::{broadcast::Sender, Mutex as AsyncMutex, OwnedMutexGuard, RwLock as AsyncRwLock},
 };
 use tracing::{debug, error, info, instrument, trace, warn, Instrument, Span};
 
-#[cfg(feature = "e2e-encryption")]
-use self::utils::JoinHandleExt as _;
 pub use self::{builder::*, client::VersionBuilderError, error::*, list::*, room::*};
 use self::{
     cache::restore_sliding_sync_state,
@@ -609,6 +609,7 @@ impl SlidingSync {
                 // aborted as soon as possible.
 
                 let client = self.inner.client.clone();
+                #[cfg(feature = "e2e-encryption")]
                 let e2ee_uploads = spawn(async move {
                     if let Err(error) = client.send_outgoing_requests().await {
                         error!(?error, "Error while sending outgoing E2EE requests");
@@ -625,6 +626,7 @@ impl SlidingSync {
                 // `e2ee_uploads`. It did run concurrently, so it should not be blocking for too
                 // long. Otherwise —if `request` has failed— `e2ee_uploads` has
                 // been dropped, so aborted.
+                #[cfg(feature = "e2e-encryption")]
                 e2ee_uploads.await.map_err(|error| Error::JoinError {
                     task_description: "e2ee_uploads".to_owned(),
                     error,
diff --git a/crates/matrix-sdk/src/sliding_sync/utils.rs b/crates/matrix-sdk/src/sliding_sync/utils.rs
deleted file mode 100644
index 0bc1e998d43..00000000000
--- a/crates/matrix-sdk/src/sliding_sync/utils.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-//! Moaaar features for Sliding Sync.
-
-use std::{
-    future::Future,
-    pin::Pin,
-    task::{Context, Poll},
-};
-
-use tokio::task::{JoinError, JoinHandle};
-
-/// Private type to ensure a task is aborted on drop.
-pub(crate) struct AbortOnDrop<T>(JoinHandle<T>);
-
-impl<T> AbortOnDrop<T> {
-    fn new(join_handle: JoinHandle<T>) -> Self {
-        Self(join_handle)
-    }
-}
-
-impl<T> Drop for AbortOnDrop<T> {
-    fn drop(&mut self) {
-        self.0.abort();
-    }
-}
-
-impl<T> Future for AbortOnDrop<T> {
-    type Output = Result<T, JoinError>;
-
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        Pin::new(&mut self.0).poll(cx)
-    }
-}
-
-/// Private trait to create a `AbortOnDrop` from a `JoinHandle`.
-pub(crate) trait JoinHandleExt<T> {
-    fn abort_on_drop(self) -> AbortOnDrop<T>;
-}
-
-impl<T> JoinHandleExt<T> for JoinHandle<T> {
-    fn abort_on_drop(self) -> AbortOnDrop<T> {
-        AbortOnDrop::new(self)
-    }
-}