Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/matrix-sdk-ffi/changelog.d/6672.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose client-level presence configuration with optional immediate updates.
16 changes: 15 additions & 1 deletion bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ use crate::{
room_preview::RoomPreview,
ruma::{
AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
MediaPreviews, MediaSource, RoomAccountDataEvent,
MediaPreviews, MediaSource, PresenceState, RoomAccountDataEvent,
},
runtime::get_runtime_handle,
spaces::SpaceService,
Expand Down Expand Up @@ -1175,6 +1175,20 @@ impl Client {
self.inner.available_sliding_sync_versions().await.into_iter().map(Into::into).collect()
}

/// Set the presence state for the current user.
///
/// This updates the presence state used by future generated sync requests,
/// regardless of `immediate`. The initial default is `Unavailable`. If
/// `immediate` is `true`, it also sends an immediate presence update to the
/// homeserver.
pub async fn set_presence(
&self,
presence: PresenceState,
immediate: bool,
) -> Result<(), ClientError> {
Ok(self.inner.set_presence(presence.into(), None, immediate).await?)
}

/// Sets the [ClientDelegate] which will inform about authentication errors.
/// Returns an error if the delegate was already set.
pub fn set_delegate(
Expand Down
30 changes: 30 additions & 0 deletions bindings/matrix-sdk-ffi/src/ruma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use ruma::{
},
},
matrix_uri::MatrixId as RumaMatrixId,
presence::PresenceState as RumaPresenceState,
push::{
ConditionalPushRule as RumaConditionalPushRule, PatternedPushRule as RumaPatternedPushRule,
Ruleset as RumaRuleset, SimplePushRule as RumaSimplePushRule,
Expand Down Expand Up @@ -190,6 +191,35 @@ impl From<&RumaMatrixId> for MatrixId {
}
}

#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum, Default)]
pub enum PresenceState {
Online,
Offline,
#[default]
Unavailable,
}

impl From<PresenceState> for RumaPresenceState {
fn from(value: PresenceState) -> Self {
match value {
PresenceState::Online => Self::Online,
PresenceState::Offline => Self::Offline,
PresenceState::Unavailable => Self::Unavailable,
}
}
}

impl From<RumaPresenceState> for PresenceState {
fn from(value: RumaPresenceState) -> Self {
match value {
RumaPresenceState::Online => Self::Online,
RumaPresenceState::Offline => Self::Offline,
RumaPresenceState::Unavailable => Self::Unavailable,
_ => Self::default(),
}
}
}

#[matrix_sdk_ffi_macros::export]
pub fn message_event_content_new(
msgtype: MessageType,
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-ui/changelog.d/6672.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make `matrix-sdk-ui` sliding sync services use the client-owned sync presence
value for generated requests, so background sync flows can avoid marking the
user online without UI-service-specific presence APIs.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ use wiremock::{
};

use crate::{
sliding_sync::{PartialSlidingSyncRequest, SlidingSyncMatcher, check_requests},
sliding_sync::{
PartialSlidingSyncRequest, SlidingSyncMatcher, assert_sliding_sync_presence_for_conn_ids,
check_requests,
},
sliding_sync_then_assert_request_and_fake_response,
};

Expand Down Expand Up @@ -170,6 +173,24 @@ async fn setup_mocking_sliding_sync_server(server: &MockServer) -> MockGuard {
.await
}

#[async_test]
async fn test_encryption_sync_default_sync_presence_is_online() -> anyhow::Result<()> {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;

let _guard = setup_mocking_sliding_sync_server(&server).await;

let sync_permit = Arc::new(AsyncMutex::new(EncryptionSyncPermit::new_for_testing()));
let sync_permit_guard = sync_permit.lock_owned().await;
let encryption_sync = EncryptionSyncService::new(client, None).await?;

encryption_sync.run_fixed_iterations(1, sync_permit_guard).await?;

assert_sliding_sync_presence_for_conn_ids(&server, None, &["encryption"]).await;

Ok(())
}

#[async_test]
async fn test_encryption_sync_one_fixed_iteration() -> anyhow::Result<()> {
let server = MatrixMockServer::new().await;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ use wiremock::{
matchers::{header, method, path},
};

use crate::sliding_sync::{PartialSlidingSyncRequest, SlidingSyncMatcher, check_requests};
use crate::sliding_sync::{
PartialSlidingSyncRequest, SlidingSyncMatcher, assert_sliding_sync_presence_for_conn_ids,
check_requests,
};

#[async_test]
async fn test_notification_client_with_context() {
Expand Down Expand Up @@ -494,6 +497,7 @@ async fn test_notification_client_sliding_sync() {
})],
)
.await;
assert_sliding_sync_presence_for_conn_ids(&server, None, &["notifications"]).await;

let Some(Ok(item)) = result.remove(event_id) else {
panic!("fetching notification for {event_id} failed");
Expand Down
41 changes: 41 additions & 0 deletions crates/matrix-sdk-ui/tests/integration/sliding_sync.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Helpers for integration tests involving sliding sync.

use std::collections::BTreeSet;

use wiremock::{Match, MockServer, Request, http::Method};

pub(crate) async fn check_requests(server: &MockServer, expected_requests: &[serde_json::Value]) {
Expand Down Expand Up @@ -167,3 +169,42 @@ macro_rules! sliding_sync_then_assert_request_and_fake_response {
(@assertion_config >=) => { assert_json_diff::Config::new(assert_json_diff::CompareMode::Inclusive) };
(@assertion_config =) => { assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict) };
}

pub(crate) async fn assert_sliding_sync_presence_for_conn_ids(
server: &MockServer,
expected_presence: Option<&str>,
expected_conn_ids: &[&str],
) {
let num_expected_conn_ids = expected_conn_ids.len();
let expected_conn_ids =
expected_conn_ids.iter().map(|conn_id| (*conn_id).to_owned()).collect::<BTreeSet<_>>();
assert_eq!(expected_conn_ids.len(), num_expected_conn_ids, "duplicate expected conn IDs");

let mut seen_conn_ids = BTreeSet::new();

for request in &server.received_requests().await.expect("Request recording has been disabled") {
if !SlidingSyncMatcher.matches(request) {
continue;
}

let json_value = serde_json::from_slice::<serde_json::Value>(&request.body).unwrap();
let Some(conn_id) = json_value.get("conn_id").and_then(|obj| obj.as_str()) else {
panic!("sliding sync request missing conn_id: {json_value:?}");
};
assert!(
expected_conn_ids.contains(conn_id),
"unexpected conn id seen server side: {conn_id}"
);

seen_conn_ids.insert(conn_id.to_owned());

let set_presence = request
.url
.query_pairs()
.find_map(|(key, value)| (key == "set_presence").then_some(value.into_owned()));

assert_eq!(set_presence.as_deref(), expected_presence, "conn_id: {conn_id}");
}

assert_eq!(seen_conn_ids, expected_conn_ids);
}
48 changes: 47 additions & 1 deletion crates/matrix-sdk-ui/tests/integration/sync_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ use assert_matches::assert_matches;
use matrix_sdk::{assert_next_matches_with_timeout, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::async_test;
use matrix_sdk_ui::sync_service::{State, SyncService};
use ruma::presence::PresenceState;
use serde_json::json;
use stream_assert::{assert_next_matches, assert_pending};
use wiremock::{Match as _, Mock, MockGuard, MockServer, Request, ResponseTemplate};

use crate::sliding_sync::{PartialSlidingSyncRequest, SlidingSyncMatcher};
use crate::sliding_sync::{
PartialSlidingSyncRequest, SlidingSyncMatcher, assert_sliding_sync_presence_for_conn_ids,
};

/// Sets up a sliding sync server that use different `pos` values for the
/// encrptyion and the room sync.
Expand Down Expand Up @@ -205,6 +208,49 @@ async fn test_sync_service_state() -> anyhow::Result<()> {
Ok(())
}

#[async_test]
async fn test_sync_service_client_sync_presence_is_used_by_both_syncs() -> anyhow::Result<()> {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;

let encryption_pos = Arc::new(Mutex::new(0));
let room_pos = Arc::new(Mutex::new(0));
let _guard = setup_mocking_sliding_sync_server(&server, encryption_pos, room_pos).await;

let sync_service = SyncService::builder(client).build().await.unwrap();
sync_service.start().await;

tokio::time::sleep(Duration::from_millis(150)).await;

sync_service.stop().await;

assert_sliding_sync_presence_for_conn_ids(&server, None, &["encryption", "room-list"]).await;

let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.set_presence(PresenceState::Offline, None, false).await?;

let encryption_pos = Arc::new(Mutex::new(0));
let room_pos = Arc::new(Mutex::new(0));
let _guard = setup_mocking_sliding_sync_server(&server, encryption_pos, room_pos).await;

let sync_service = SyncService::builder(client).build().await.unwrap();
sync_service.start().await;

tokio::time::sleep(Duration::from_millis(150)).await;

sync_service.stop().await;

assert_sliding_sync_presence_for_conn_ids(
&server,
Some("offline"),
&["encryption", "room-list"],
)
.await;

Ok(())
}

#[async_test]
async fn test_sync_service_offline_mode() {
let mock_server = MatrixMockServer::new().await;
Expand Down
4 changes: 4 additions & 0 deletions crates/matrix-sdk/changelog.d/6672.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add client-owned presence configuration that can optionally send an immediate
presence update. Generated sync requests use the client presence by default,
which starts as unavailable, while classic sync settings can still override it
per request.
8 changes: 7 additions & 1 deletion crates/matrix-sdk/src/client/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ use std::collections::HashMap;
use std::path::Path;
#[cfg(any(feature = "experimental-search", feature = "sqlite"))]
use std::path::PathBuf;
use std::{collections::BTreeSet, fmt, sync::Arc};
use std::{
collections::BTreeSet,
fmt,
sync::{Arc, RwLock as StdRwLock},
};

#[cfg(feature = "sqlite")]
use futures_util::try_join;
Expand All @@ -41,6 +45,7 @@ use reqwest::Certificate;
use ruma::{
OwnedServerName, ServerName,
api::{MatrixVersion, SupportedVersions, error::FromHttpResponseError},
presence::PresenceState,
};
use thiserror::Error;
#[cfg(feature = "experimental-search")]
Expand Down Expand Up @@ -648,6 +653,7 @@ impl ClientBuilder {
server,
homeserver,
sliding_sync_version,
Arc::new(StdRwLock::new(PresenceState::Online)),
http_client,
base_client,
supported_versions,
Expand Down
Loading
Loading