From 18c34434bcdda65117f61355002258e7de2cc242 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Fri, 29 Mar 2024 18:35:02 -0700 Subject: [PATCH] add outgoing federation typing and conf items Signed-off-by: Jason Volk --- conduwuit-example.toml | 16 +++++++++ src/api/client_server/typing.rs | 11 +++--- src/api/server_server.rs | 8 ++++- src/config/mod.rs | 25 +++++++++++++ src/service/rooms/edus/typing/mod.rs | 54 ++++++++++++++++++++++++++-- 5 files changed, 105 insertions(+), 9 deletions(-) diff --git a/conduwuit-example.toml b/conduwuit-example.toml index d90e4ddf..435e1260 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -563,6 +563,22 @@ url_preview_check_root_domain = false # Defaults to true. #allow_incoming_read_receipts = true +# Config option to control outgoing typing updates to federation. Defaults to true. +#allow_outgoing_typing = true + +# Config option to control incoming typing updates from federation. Defaults to true. +#allow_incoming_typing = true + +# Config option to control maximum time federation user can indicate typing. +#typing_federation_timeout_s = 30 + +# Config option to control minimum time local client can indicate typing. This does not override +# a client's request to stop typing. It only enforces a minimum value in case of no stop request. +#typing_client_timeout_min_s = 15 + +# Config option to control maximum time local client can indicate typing. +#typing_client_timeout_max_s = 45 + # Other options not in [global]: # diff --git a/src/api/client_server/typing.rs b/src/api/client_server/typing.rs index 87a66744..40510b88 100644 --- a/src/api/client_server/typing.rs +++ b/src/api/client_server/typing.rs @@ -21,15 +21,16 @@ pub async fn create_typing_event_route( } if let Typing::Yes(duration) = body.state { + let duration = utils::clamp( + duration.as_millis() as u64, + services().globals.config.typing_client_timeout_min_s * 1000, + services().globals.config.typing_client_timeout_max_s * 1000, + ); services() .rooms .edus .typing - .typing_add( - sender_user, - &body.room_id, - duration.as_millis() as u64 + utils::millis_since_unix_epoch(), - ) + .typing_add(sender_user, &body.room_id, utils::millis_since_unix_epoch() + duration) .await?; } else { services() diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 69050a5f..3a51a15d 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -956,17 +956,23 @@ pub async fn send_transaction_message_route( } }, Edu::Typing(typing) => { + if !services().globals.config.allow_incoming_typing { + continue; + } + if services() .rooms .state_cache .is_joined(&typing.user_id, &typing.room_id)? { if typing.typing { + let timeout = utils::millis_since_unix_epoch() + + services().globals.config.typing_federation_timeout_s * 1000; services() .rooms .edus .typing - .typing_add(&typing.user_id, &typing.room_id, 3000 + utils::millis_since_unix_epoch()) + .typing_add(&typing.user_id, &typing.room_id, timeout) .await?; } else { services() diff --git a/src/config/mod.rs b/src/config/mod.rs index 587a291e..fc538522 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -195,6 +195,17 @@ pub struct Config { #[serde(default = "true_fn")] pub allow_incoming_read_receipts: bool, + #[serde(default = "true_fn")] + pub allow_outgoing_typing: bool, + #[serde(default = "true_fn")] + pub allow_incoming_typing: bool, + #[serde(default = "default_typing_federation_timeout_s")] + pub typing_federation_timeout_s: u64, + #[serde(default = "default_typing_client_timeout_min_s")] + pub typing_client_timeout_min_s: u64, + #[serde(default = "default_typing_client_timeout_max_s")] + pub typing_client_timeout_max_s: u64, + #[serde(default)] pub zstd_compression: bool, #[serde(default)] @@ -390,6 +401,14 @@ impl fmt::Display for Config { "Block non-admin room invites (local and remote, admins can still send and receive invites)", &self.block_non_admin_invites.to_string(), ), + ("Allow outgoing federated typing", &self.allow_outgoing_typing.to_string()), + ("Allow incoming federated typing", &self.allow_incoming_typing.to_string()), + ( + "Incoming federated typing timeout", + &self.typing_federation_timeout_s.to_string(), + ), + ("Client typing timeout minimum", &self.typing_client_timeout_min_s.to_string()), + ("Client typing timeout maxmimum", &self.typing_client_timeout_max_s.to_string()), ("Allow device name federation", &self.allow_device_name_federation.to_string()), ("Notification push path", &self.notification_push_path), ("Allow room creation", &self.allow_room_creation.to_string()), @@ -639,6 +658,12 @@ fn default_presence_idle_timeout_s() -> u64 { 2 * 60 } fn default_presence_offline_timeout_s() -> u64 { 15 * 60 } +fn default_typing_federation_timeout_s() -> u64 { 30 } + +fn default_typing_client_timeout_min_s() -> u64 { 15 } + +fn default_typing_client_timeout_max_s() -> u64 { 45 } + fn default_rocksdb_recovery_mode() -> u8 { 1 } fn default_rocksdb_log_level() -> String { "error".to_owned() } diff --git a/src/service/rooms/edus/typing/mod.rs b/src/service/rooms/edus/typing/mod.rs index 8437597f..6d98937f 100644 --- a/src/service/rooms/edus/typing/mod.rs +++ b/src/service/rooms/edus/typing/mod.rs @@ -1,7 +1,12 @@ use std::collections::BTreeMap; -use ruma::{events::SyncEphemeralRoomEvent, OwnedRoomId, OwnedUserId, RoomId, UserId}; +use ruma::{ + api::federation::transactions::edu::{Edu, TypingContent}, + events::SyncEphemeralRoomEvent, + OwnedRoomId, OwnedUserId, RoomId, UserId, +}; use tokio::sync::{broadcast, RwLock}; +use tracing::debug; use crate::{services, utils, Result}; @@ -16,6 +21,8 @@ impl Service { /// Sets a user as typing until the timeout timestamp is reached or /// roomtyping_remove is called. pub async fn typing_add(&self, user_id: &UserId, room_id: &RoomId, timeout: u64) -> Result<()> { + debug!("typing add {:?} in {:?} timeout:{:?}", user_id, room_id, timeout); + // update clients self.typing .write() .await @@ -27,11 +34,19 @@ impl Service { .await .insert(room_id.to_owned(), services().globals.next_count()?); _ = self.typing_update_sender.send(room_id.to_owned()); + + // update federation + if user_id.server_name() == services().globals.server_name() { + self.federation_send(room_id, user_id, true)?; + } + Ok(()) } /// Removes a user from typing before the timeout is reached. pub async fn typing_remove(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> { + debug!("typing remove {:?} in {:?}", user_id, room_id); + // update clients self.typing .write() .await @@ -43,6 +58,12 @@ impl Service { .await .insert(room_id.to_owned(), services().globals.next_count()?); _ = self.typing_update_sender.send(room_id.to_owned()); + + // update federation + if user_id.server_name() == services().globals.server_name() { + self.federation_send(room_id, user_id, false)?; + } + Ok(()) } @@ -80,14 +101,23 @@ impl Service { if !removable.is_empty() { let typing = &mut self.typing.write().await; let room = typing.entry(room_id.to_owned()).or_default(); - for user in removable { - room.remove(&user); + for user in &removable { + debug!("typing maintain remove {:?} in {:?}", &user, room_id); + room.remove(user); } + // update clients self.last_typing_update .write() .await .insert(room_id.to_owned(), services().globals.next_count()?); _ = self.typing_update_sender.send(room_id.to_owned()); + + // update federation + for user in removable { + if user.server_name() == services().globals.server_name() { + self.federation_send(room_id, &user, false)?; + } + } } Ok(()) @@ -121,4 +151,22 @@ impl Service { }, }) } + + fn federation_send(&self, room_id: &RoomId, user_id: &UserId, typing: bool) -> Result<()> { + debug_assert!( + user_id.server_name() == services().globals.server_name(), + "tried to broadcast typing status of remote user", + ); + if !services().globals.config.allow_outgoing_typing { + return Ok(()); + } + + let edu = Edu::Typing(TypingContent::new(room_id.to_owned(), user_id.to_owned(), typing)); + + services() + .sending + .send_edu_room(room_id, serde_json::to_vec(&edu).expect("Serialized Edu::Typing"))?; + + Ok(()) + } }