mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-14 12:46:49 +00:00
feat(sync): Add MSC4354 sticky events
This commit is contained in:
1 parent
7789399ba5
commit
43197f695c
25 files changed
+572
-18
No files matched your search
@@ -541,6 +541,7 @@ pub(crate) async fn create_room_route(
|
||||
&content,
|
||||
&state_key,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
|
||||
+11
-1
@@ -1,7 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, err, matrix::pdu::PartialPdu, utils};
|
||||
use conduwuit::{
|
||||
Err, Result, err,
|
||||
matrix::pdu::{PartialPdu, sticky},
|
||||
utils,
|
||||
};
|
||||
use ruma::{api::client::message::send_message_event, events::MessageLikeEventType};
|
||||
use serde_json::from_str;
|
||||
|
||||
@@ -81,6 +85,12 @@ pub(crate) async fn send_message_event_route(
|
||||
PartialPdu {
|
||||
event_type: body.event_type.clone().into(),
|
||||
content,
|
||||
sticky: services
|
||||
.config
|
||||
.allow_sticky_events
|
||||
.then_some(body.sticky_duration_ms)
|
||||
.flatten()
|
||||
.map(sticky::object),
|
||||
unsigned: Some(unsigned),
|
||||
timestamp: if body.identity.is_appservice() {
|
||||
body.timestamp
|
||||
|
||||
@@ -49,6 +49,7 @@ pub(crate) async fn send_state_event_for_key_route(
|
||||
&body.event_type,
|
||||
&body.body.body,
|
||||
&body.state_key,
|
||||
body.sticky_duration_ms,
|
||||
if body.identity.is_appservice() {
|
||||
body.timestamp
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@ use conduwuit::{
|
||||
Result, at, debug_warn, err, extract_variant,
|
||||
matrix::{
|
||||
Event,
|
||||
pdu::{PduCount, PduEvent},
|
||||
pdu::{PduCount, PduEvent, sticky},
|
||||
},
|
||||
trace,
|
||||
utils::{
|
||||
@@ -20,12 +20,12 @@ use futures::{
|
||||
future::{OptionFuture, join, join3, join4, try_join, try_join3},
|
||||
};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
api::client::sync::sync_events::{
|
||||
UnreadNotificationsCount,
|
||||
v3::{
|
||||
Ephemeral, JoinedRoom, RoomAccountData, RoomSummary, State as RoomState, StateEvents,
|
||||
Timeline,
|
||||
Sticky, Timeline,
|
||||
},
|
||||
},
|
||||
assign,
|
||||
@@ -42,7 +42,7 @@ use tokio::pin;
|
||||
|
||||
use super::{load_timeline, share_encrypted_room};
|
||||
use crate::client::{
|
||||
TimelinePdus, ignored_filter,
|
||||
TimelinePdus, ignored_filter, is_ignored_pdu,
|
||||
sync::v3::{
|
||||
DEFAULT_TIMELINE_LIMIT, DeviceListUpdates, SyncContext, prepare_lazily_loaded_members,
|
||||
state::{build_state_incremental, build_state_initial},
|
||||
@@ -76,6 +76,7 @@ pub(super) async fn load_joined_room(
|
||||
StateAndTimeline {
|
||||
state_events,
|
||||
timeline,
|
||||
sticky,
|
||||
summary,
|
||||
notification_counts,
|
||||
device_list_updates,
|
||||
@@ -111,6 +112,7 @@ pub(super) async fn load_joined_room(
|
||||
RoomState::Before(state_events)
|
||||
},
|
||||
ephemeral,
|
||||
sticky,
|
||||
unread_thread_notifications: BTreeMap::new(),
|
||||
});
|
||||
|
||||
@@ -251,11 +253,82 @@ async fn build_ephemeral(
|
||||
struct StateAndTimeline {
|
||||
state_events: Vec<PduEvent>,
|
||||
timeline: Timeline,
|
||||
sticky: Sticky,
|
||||
summary: Option<RoomSummary>,
|
||||
notification_counts: Option<UnreadNotificationsCount>,
|
||||
device_list_updates: DeviceListUpdates,
|
||||
}
|
||||
|
||||
async fn build_sticky_events(
|
||||
services: &Services,
|
||||
sync_context: SyncContext<'_>,
|
||||
room_id: &RoomId,
|
||||
timeline: &TimelinePdus,
|
||||
joined_since_last_sync: bool,
|
||||
) -> Sticky {
|
||||
if !services.config.allow_sticky_events {
|
||||
return Sticky::new();
|
||||
}
|
||||
|
||||
let SyncContext {
|
||||
syncing_user,
|
||||
last_sync_end_count,
|
||||
current_count,
|
||||
..
|
||||
} = sync_context;
|
||||
|
||||
let timeline_event_ids: HashSet<_> =
|
||||
timeline.pdus.iter().map(|(_, pdu)| &pdu.event_id).collect();
|
||||
let now = u64::from(MilliSecondsSinceUnixEpoch::now().get());
|
||||
|
||||
// on join the client needs every unexpired sticky event, not just the ones
|
||||
// since its last sync
|
||||
let since = (!joined_since_last_sync)
|
||||
.then_some(last_sync_end_count)
|
||||
.flatten()
|
||||
.map(PduCount::Normal);
|
||||
|
||||
// nothing older than this can still be sticky, so the walk back never goes far
|
||||
let oldest_sticky_ts = now.saturating_sub(sticky::MAX_DURATION_MS);
|
||||
|
||||
let mut events = Vec::new();
|
||||
let pdus = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(room_id, Some(PduCount::Normal(current_count).saturating_add(1)))
|
||||
.ignore_err();
|
||||
|
||||
pin!(pdus);
|
||||
while let Some((count, mut pdu)) = pdus.next().await {
|
||||
if since.is_some_and(|since| count <= since)
|
||||
|| u64::from(pdu.origin_server_ts) < oldest_sticky_ts
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
let is_sticky = pdu
|
||||
.sticky
|
||||
.as_deref()
|
||||
.is_some_and(|sticky| sticky::is_sticky(pdu.origin_server_ts, sticky, now));
|
||||
|
||||
if !is_sticky
|
||||
|| timeline_event_ids.contains(&pdu.event_id)
|
||||
|| is_ignored_pdu(services, &pdu, syncing_user)
|
||||
.await
|
||||
.unwrap_or(true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pdu.set_unsigned(Some(syncing_user));
|
||||
events.push(Event::into_format(pdu));
|
||||
}
|
||||
|
||||
events.reverse();
|
||||
|
||||
assign!(Sticky::new(), { events })
|
||||
}
|
||||
|
||||
/// Compute changes to the room's state and timeline.
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn build_state_and_timeline(
|
||||
@@ -274,6 +347,9 @@ async fn build_state_and_timeline(
|
||||
check_joined_since_last_sync(services, shortstatehashes, sync_context),
|
||||
)
|
||||
.await?;
|
||||
let sticky =
|
||||
build_sticky_events(services, sync_context, room_id, &timeline, joined_since_last_sync)
|
||||
.await;
|
||||
|
||||
let state_events = build_state_events(
|
||||
services,
|
||||
@@ -342,6 +418,7 @@ async fn build_state_and_timeline(
|
||||
prev_batch: prev_batch.as_ref().map(ToString::to_string),
|
||||
events: filtered_timeline,
|
||||
}),
|
||||
sticky,
|
||||
summary,
|
||||
notification_counts,
|
||||
device_list_updates,
|
||||
|
||||
@@ -328,6 +328,7 @@ fn create_dummy_leave_event(
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
kind: TimelineEventType::RoomMember,
|
||||
content: RawValue::from_string(r#"{"membership": "leave"}"#.to_owned()).unwrap(),
|
||||
sticky: None,
|
||||
state_key: Some(syncing_user.as_str().into()),
|
||||
unsigned: None,
|
||||
// The following keys are dropped on conversion
|
||||
|
||||
@@ -21,12 +21,19 @@ use crate::Ruma;
|
||||
/// Note: Unstable features are used while developing new features. Clients
|
||||
/// should avoid using unstable features in their stable releases
|
||||
pub(crate) async fn get_supported_versions_route(
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<get_supported_versions::Request>,
|
||||
) -> Result<get_supported_versions::Response> {
|
||||
Ok(assign!(
|
||||
get_supported_versions::Response::new(versions()),
|
||||
{ unstable_features: unstable_features() }
|
||||
))
|
||||
let mut unstable_features = unstable_features();
|
||||
|
||||
if services.config.allow_sticky_events {
|
||||
// sticky events (https://github.com/matrix-org/matrix-spec-proposals/pull/4354)
|
||||
unstable_features.insert("org.matrix.msc4354".to_owned(), true);
|
||||
}
|
||||
|
||||
Ok(assign!(get_supported_versions::Response::new(versions()), {
|
||||
unstable_features
|
||||
}))
|
||||
}
|
||||
|
||||
/// # `GET /_continuwuity/server_version`
|
||||
|
||||
Reference in new issue
Block a user