From 1f0b8ef0f3fa2a4443dbe4d8cb64d4d765af4e6f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Mon, 26 May 2025 17:01:26 +0100 Subject: [PATCH] feat: Typing notifications in simplified sliding sync What's missing? Being able to use separate rooms & lists for typing indicators. At the moment, we use the same ones as we use for the timeline, as todo_rooms is quite intertwined. We need to disentangle this to get that functionality, although I'm not sure if clients use it. --- .typos.toml | 2 +- Cargo.lock | 22 ++++++------ Cargo.toml | 4 +-- src/api/client/sync/v3.rs | 2 +- src/api/client/sync/v5.rs | 62 +++++++++++++++++++++++++++++++++ src/service/rooms/typing/mod.rs | 22 ++++++++---- 6 files changed, 92 insertions(+), 22 deletions(-) diff --git a/.typos.toml b/.typos.toml index 41c81085..73a5d6ea 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,5 +1,5 @@ [files] -extend-exclude = ["*.csr"] +extend-exclude = ["*.csr", "*.lock"] [default.extend-words] "allocatedp" = "allocatedp" diff --git a/Cargo.lock b/Cargo.lock index 160be0c7..3e80849c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3695,7 +3695,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "assign", "js_int", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3727,7 +3727,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "assign", @@ -3750,7 +3750,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "base64 0.22.1", @@ -3782,7 +3782,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "indexmap 2.9.0", @@ -3807,7 +3807,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "bytes", "headers", @@ -3829,7 +3829,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "thiserror 2.0.12", @@ -3838,7 +3838,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3848,7 +3848,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "cfg-if", "proc-macro-crate", @@ -3863,7 +3863,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3875,7 +3875,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 1abff107..80aacf39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -350,7 +350,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +rev = "a48665b682be1016cea53ea5e7787442dfe7c1de" features = [ "compat", "rand", @@ -381,7 +381,7 @@ features = [ "unstable-msc4121", "unstable-msc4125", "unstable-msc4186", - "unstable-msc4203", # sending to-device events to appservices + "unstable-msc4203", # sending to-device events to appservices "unstable-msc4210", # remove legacy mentions "unstable-extensible-events", "unstable-pdu", diff --git a/src/api/client/sync/v3.rs b/src/api/client/sync/v3.rs index 8eac6b66..41b3d17f 100644 --- a/src/api/client/sync/v3.rs +++ b/src/api/client/sync/v3.rs @@ -808,7 +808,7 @@ async fn load_joined_room( let typings = services .rooms .typing - .typings_all(room_id, sender_user) + .typings_event_for_user(room_id, sender_user) .await?; Ok(vec![serde_json::from_str(&serde_json::to_string(&typings)?)?]) diff --git a/src/api/client/sync/v5.rs b/src/api/client/sync/v5.rs index f3fc0f44..65a820a0 100644 --- a/src/api/client/sync/v5.rs +++ b/src/api/client/sync/v5.rs @@ -33,6 +33,7 @@ use ruma::{ events::{ AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType, room::member::{MembershipState, RoomMemberEventContent}, + typing::TypingEventContent, }, serde::Raw, uint, @@ -205,6 +206,9 @@ pub(crate) async fn sync_events_v5_route( _ = tokio::time::timeout(duration, watcher).await; } + let typing = collect_typing_events(services, sender_user, &body, &todo_rooms).await?; + response.extensions.typing = typing; + trace!( rooms = ?response.rooms.len(), account_data = ?response.extensions.account_data.rooms.len(), @@ -288,6 +292,8 @@ where Rooms: Iterator + Clone + Send + 'a, AllRooms: Iterator + Clone + Send + 'a, { + // TODO MSC4186: Implement remaining list filters: is_dm, is_encrypted, + // room_types. for (list_id, list) in &body.lists { let active_rooms: Vec<_> = match list.filters.as_ref().and_then(|f| f.is_invite) { | None => all_rooms.clone().collect(), @@ -665,6 +671,62 @@ where } Ok(rooms) } + +async fn collect_typing_events( + services: &Services, + sender_user: &UserId, + body: &sync_events::v5::Request, + todo_rooms: &TodoRooms, +) -> Result { + if !body.extensions.typing.enabled.unwrap_or(false) { + return Ok(sync_events::v5::response::Typing::default()); + } + let rooms: Vec<_> = body.extensions.typing.rooms.clone().unwrap_or_else(|| { + body.room_subscriptions + .keys() + .map(ToOwned::to_owned) + .collect() + }); + let lists: Vec<_> = body + .extensions + .typing + .lists + .clone() + .unwrap_or_else(|| body.lists.keys().map(ToOwned::to_owned).collect::>()); + + if rooms.is_empty() && lists.is_empty() { + return Ok(sync_events::v5::response::Typing::default()); + } + + let mut typing_response = sync_events::v5::response::Typing::default(); + for (room_id, (required_state_request, timeline_limit, roomsince)) in todo_rooms { + if services.rooms.typing.last_typing_update(room_id).await? <= *roomsince { + continue; + } + + match services + .rooms + .typing + .typing_users_for_user(room_id, sender_user) + .await + { + | Ok(typing_users) => { + typing_response.rooms.insert( + room_id.to_owned(), // Already OwnedRoomId + Raw::new(&sync_events::v5::response::SyncTypingEvent { + content: TypingEventContent::new(typing_users), + })?, + ); + }, + | Err(e) => { + warn!(%room_id, "Failed to get typing events for room: {}", e); + }, + } + } + + Ok(typing_response) +} + async fn collect_account_data( services: &Services, (sender_user, _, globalsince, body): (&UserId, &DeviceId, u64, &sync_events::v5::Request), diff --git a/src/service/rooms/typing/mod.rs b/src/service/rooms/typing/mod.rs index a81ee95c..28b9dfa7 100644 --- a/src/service/rooms/typing/mod.rs +++ b/src/service/rooms/typing/mod.rs @@ -179,18 +179,15 @@ impl Service { .unwrap_or(0)) } - /// Returns a new typing EDU. - pub async fn typings_all( + pub async fn typing_users_for_user( &self, room_id: &RoomId, sender_user: &UserId, - ) -> Result> { + ) -> Result> { let room_typing_indicators = self.typing.read().await.get(room_id).cloned(); let Some(typing_indicators) = room_typing_indicators else { - return Ok(SyncEphemeralRoomEvent { - content: ruma::events::typing::TypingEventContent { user_ids: Vec::new() }, - }); + return Ok(Vec::new()); }; let user_ids: Vec<_> = typing_indicators @@ -207,8 +204,19 @@ impl Service { .collect() .await; + Ok(user_ids) + } + + /// Returns a new typing EDU. + pub async fn typings_event_for_user( + &self, + room_id: &RoomId, + sender_user: &UserId, + ) -> Result> { Ok(SyncEphemeralRoomEvent { - content: ruma::events::typing::TypingEventContent { user_ids }, + content: ruma::events::typing::TypingEventContent { + user_ids: self.typing_users_for_user(room_id, sender_user).await?, + }, }) }