feat(ui): Expose information about room key bundle forwarder.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Skye Elliot
2026-01-06 11:49:11 +00:00
parent 21cad56213
commit 0a5a22ec6f
8 changed files with 122 additions and 1 deletions
@@ -1007,6 +1007,8 @@ pub struct EventTimelineItem {
event_or_transaction_id: EventOrTransactionId,
sender: String,
sender_profile: ProfileDetails,
forwarder: Option<String>,
forwarder_profile: Option<ProfileDetails>,
is_own: bool,
is_editable: bool,
content: TimelineItemContent,
@@ -1030,6 +1032,8 @@ impl From<matrix_sdk_ui::timeline::EventTimelineItem> for EventTimelineItem {
event_or_transaction_id: item.identifier().into(),
sender: item.sender().to_string(),
sender_profile: item.sender_profile().clone().into(),
forwarder: item.forwarder().map(ToString::to_string),
forwarder_profile: item.forwarder_profile().map(Into::into),
is_own: item.is_own(),
is_editable: item.is_editable(),
content: item.content().clone().into(),
@@ -1085,6 +1089,21 @@ impl From<TimelineDetails<Profile>> for ProfileDetails {
}
}
impl From<&TimelineDetails<Profile>> for ProfileDetails {
fn from(details: &TimelineDetails<Profile>) -> Self {
match details {
TimelineDetails::Unavailable => Self::Unavailable,
TimelineDetails::Pending => Self::Pending,
TimelineDetails::Ready(profile) => Self::Ready {
display_name: profile.display_name.clone(),
display_name_ambiguous: profile.display_name_ambiguous,
avatar_url: profile.avatar_url.as_ref().map(ToString::to_string),
},
TimelineDetails::Error(e) => Self::Error { message: e.to_string() },
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct PollData {
question: String,
@@ -236,6 +236,8 @@ mod tests {
TimelineItemKind::Event(EventTimelineItem::new(
owned_user_id!("@u:s.to"),
TimelineDetails::Pending,
None,
None,
timestamp(),
TimelineItemContent::MsgLike(MsgLikeContent::redacted()),
event_kind,
@@ -262,6 +264,8 @@ mod tests {
TimelineItemKind::Event(EventTimelineItem::new(
owned_user_id!("@u:s.to"),
TimelineDetails::Pending,
None,
None,
timestamp(),
TimelineItemContent::MsgLike(MsgLikeContent::unable_to_decrypt(
EncryptedMessage::from_content(
@@ -315,6 +319,8 @@ mod tests {
TimelineItemKind::Event(EventTimelineItem::new(
owned_user_id!("@u:s.to"),
TimelineDetails::Pending,
None,
None,
timestamp(),
TimelineItemContent::message(
content.msgtype,
@@ -734,6 +734,8 @@ mod observable_items_tests {
EventTimelineItem::new(
owned_user_id!("@ivan:mnt.io"),
TimelineDetails::Unavailable,
None,
None,
MilliSecondsSinceUnixEpoch(0u32.into()),
TimelineItemContent::MsgLike(MsgLikeContent {
kind: MsgLikeKind::Message(Message {
@@ -768,6 +770,8 @@ mod observable_items_tests {
EventTimelineItem::new(
owned_user_id!("@ivan:mnt.io"),
TimelineDetails::Unavailable,
None,
None,
MilliSecondsSinceUnixEpoch(0u32.into()),
TimelineItemContent::MsgLike(MsgLikeContent {
kind: MsgLikeKind::Message(Message {
@@ -180,6 +180,8 @@ impl<P: RoomDataProvider> TimelineState<P> {
let ctx = TimelineEventContext {
sender: own_user_id,
sender_profile: own_profile,
forwarder: None,
forwarder_profile: None,
timestamp: MilliSecondsSinceUnixEpoch::now(),
read_receipts: Default::default(),
// An event sent by ourselves is never matched against push rules.
@@ -226,9 +226,23 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
| Some(action @ TimelineAction::HandleAggregation { .. }) => {
let encryption_info = event.kind.encryption_info().cloned();
let sender_profile = room_data_provider.profile_from_user_id(&sender).await;
let forwarder = encryption_info
.as_ref()
.and_then(|info| info.forwarder.as_ref())
.map(|info| info.user_id.clone());
let forwarder_profile = if let Some(ref forwarder_id) = forwarder {
Some(room_data_provider.profile_from_user_id(forwarder_id).await)
} else {
None
};
let mut ctx = TimelineEventContext {
sender,
sender_profile,
forwarder,
forwarder_profile: forwarder_profile.flatten(),
timestamp,
// These are not used when handling an aggregation.
read_receipts: Default::default(),
@@ -700,6 +714,18 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
map.get(&UnsignedEventLocation::RelationsReplace)?.encryption_info().cloned()
});
let forwarder = event
.kind
.encryption_info()
.and_then(|info| info.forwarder.as_ref())
.map(|info| info.user_id.clone());
let forwarder_profile = if let Some(ref forwarder_id) = forwarder {
Some(room_data_provider.profile_from_user_id(forwarder_id).await)
} else {
None
};
let (raw, utd_info) = match event.kind {
TimelineEventKind::UnableToDecrypt { utd_info, event } => (event, Some(utd_info)),
_ => (event.kind.into_raw(), None),
@@ -794,6 +820,8 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
let ctx = TimelineEventContext {
sender,
sender_profile,
forwarder,
forwarder_profile: forwarder_profile.flatten(),
timestamp,
read_receipts: if settings.track_read_receipts.is_enabled()
&& should_add
@@ -683,6 +683,8 @@ mod tests {
EventTimelineItem::new(
owned_user_id!("@alice:example.org"),
crate::timeline::TimelineDetails::Pending,
None,
None,
timestamp,
TimelineItemContent::MsgLike(MsgLikeContent::redacted()),
event_kind,
@@ -108,6 +108,16 @@ impl Flow {
pub(super) struct TimelineEventContext {
pub(super) sender: OwnedUserId,
pub(super) sender_profile: Option<Profile>,
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, the user ID of the forwarder.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub(super) forwarder: Option<OwnedUserId>,
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, the forwarder's profile.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub(super) forwarder_profile: Option<Profile>,
/// The event's `origin_server_ts` field (or creation time for local echo).
pub(super) timestamp: MilliSecondsSinceUnixEpoch,
pub(super) read_receipts: IndexMap<OwnedUserId, Receipt>,
@@ -762,6 +772,14 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
fn add_item(&mut self, content: TimelineItemContent) {
let sender = self.ctx.sender.to_owned();
let sender_profile = TimelineDetails::from_initial_value(self.ctx.sender_profile.clone());
let forwarder = self.ctx.forwarder.to_owned();
let forwarder_profile = self
.ctx
.forwarder
.as_ref()
.map(|_| TimelineDetails::from_initial_value(self.ctx.forwarder_profile.clone()));
let timestamp = self.ctx.timestamp;
let kind: EventTimelineItemKind = match &self.ctx.flow {
@@ -808,6 +826,8 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
let item = EventTimelineItem::new(
sender,
sender_profile,
forwarder,
forwarder_profile,
timestamp,
content,
kind,
@@ -67,6 +67,16 @@ pub struct EventTimelineItem {
pub(super) sender: OwnedUserId,
/// The sender's profile of the event.
pub(super) sender_profile: TimelineDetails<Profile>,
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, the user ID of the forwarder.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub(super) forwarder: Option<OwnedUserId>,
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, the forwarder's profile, if present.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub(super) forwarder_profile: Option<TimelineDetails<Profile>>,
/// The timestamp of the event.
pub(super) timestamp: MilliSecondsSinceUnixEpoch,
/// The content of the event.
@@ -108,15 +118,27 @@ pub(crate) enum TimelineItemHandle<'a> {
}
impl EventTimelineItem {
#[allow(clippy::too_many_arguments)]
pub(super) fn new(
sender: OwnedUserId,
sender_profile: TimelineDetails<Profile>,
forwarder: Option<OwnedUserId>,
forwarder_profile: Option<TimelineDetails<Profile>>,
timestamp: MilliSecondsSinceUnixEpoch,
content: TimelineItemContent,
kind: EventTimelineItemKind,
is_room_encrypted: bool,
) -> Self {
Self { sender, sender_profile, timestamp, content, kind, is_room_encrypted }
Self {
sender,
sender_profile,
forwarder,
forwarder_profile,
timestamp,
content,
kind,
is_room_encrypted,
}
}
/// Check whether this item is a local echo.
@@ -216,6 +238,22 @@ impl EventTimelineItem {
&self.sender_profile
}
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, returns the user ID of the forwarder.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub fn forwarder(&self) -> Option<&UserId> {
self.forwarder.as_deref()
}
/// If the keys used to decrypt this event were shared-on-invite as part of
/// an [MSC4268] key bundle, returns the profile of the forwarder.
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub fn forwarder_profile(&self) -> Option<&TimelineDetails<Profile>> {
self.forwarder_profile.as_ref()
}
/// Get the content of this item.
pub fn content(&self) -> &TimelineItemContent {
&self.content
@@ -449,6 +487,8 @@ impl EventTimelineItem {
Self {
sender: self.sender.clone(),
sender_profile: self.sender_profile.clone(),
forwarder: self.forwarder.clone(),
forwarder_profile: self.forwarder_profile.clone(),
timestamp: self.timestamp,
content,
kind,