fix: Apply redactions to room state events in database, too (#917)
fixes #890
This commit is contained in:
parent
f8b02d1a11
commit
b5bd6dfee9
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
[profile.default]
|
||||||
|
retries = 2
|
||||||
|
# kill the slow tests if they still aren't up after 180s
|
||||||
|
slow-timeout = { period = "60s", terminate-after = 3 }
|
|
@ -441,12 +441,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
|
||||||
- uses: gnunicorn/setup-matrix-synapse@main
|
- uses: michaelkaye/setup-matrix-synapse@main
|
||||||
with:
|
with:
|
||||||
uploadLogs: true
|
uploadLogs: true
|
||||||
httpPort: 8228
|
httpPort: 8228
|
||||||
disableRateLimiting: true
|
disableRateLimiting: true
|
||||||
serverName: "matrix-sdk.rs"
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
|
|
@ -2573,6 +2573,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -327,10 +327,7 @@ impl BaseClient {
|
||||||
),
|
),
|
||||||
) => {
|
) => {
|
||||||
room_info.handle_redaction(r);
|
room_info.handle_redaction(r);
|
||||||
// FIXME: Find the event in self.store (needs
|
changes.add_redaction(room_id, r.clone());
|
||||||
// something like a get_event_by_id), redact it and
|
|
||||||
// put it back via StateChanges. See
|
|
||||||
// https://github.com/matrix-org/matrix-rust-sdk/issues/607
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "e2e-encryption")]
|
#[cfg(feature = "e2e-encryption")]
|
||||||
|
|
|
@ -59,6 +59,7 @@ macro_rules! statestore_integration_tests {
|
||||||
},
|
},
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
MediaSource,
|
MediaSource,
|
||||||
|
topic::{RoomTopicEventContent, OriginalRoomTopicEvent, RedactedRoomTopicEvent},
|
||||||
},
|
},
|
||||||
AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent,
|
AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent,
|
||||||
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent,
|
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent,
|
||||||
|
@ -78,7 +79,7 @@ macro_rules! statestore_integration_tests {
|
||||||
};
|
};
|
||||||
use $crate::{
|
use $crate::{
|
||||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||||
store::{Result as StoreResult, StateChanges, StateStore},
|
store::{Result as StoreResult, StateChanges, StateStore, StateStoreExt},
|
||||||
RoomInfo, RoomType,
|
RoomInfo, RoomType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,8 +148,8 @@ macro_rules! statestore_integration_tests {
|
||||||
|
|
||||||
let topic_json: &JsonValue = &test_json::TOPIC;
|
let topic_json: &JsonValue = &test_json::TOPIC;
|
||||||
let topic_raw =
|
let topic_raw =
|
||||||
serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).unwrap();
|
serde_json::from_value::<Raw<AnySyncStateEvent>>(topic_json.clone()).expect("can create sync-state-event for topic");
|
||||||
let topic_event = topic_raw.deserialize().unwrap();
|
let topic_event = topic_raw.deserialize().expect("can deserialize raw topic");
|
||||||
room.handle_state_event(&topic_event);
|
room.handle_state_event(&topic_event);
|
||||||
changes.add_state_event(room_id, topic_event, topic_raw);
|
changes.add_state_event(room_id, topic_event, topic_raw);
|
||||||
|
|
||||||
|
@ -284,6 +285,55 @@ macro_rules! statestore_integration_tests {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_topic_redaction() -> StoreResult<()> {
|
||||||
|
let room_id = room_id();
|
||||||
|
let inner_store = get_store().await?;
|
||||||
|
|
||||||
|
let store = Arc::new(inner_store);
|
||||||
|
populate_store(store.clone()).await?;
|
||||||
|
|
||||||
|
assert!(store.get_sync_token().await?.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
store
|
||||||
|
.get_state_event_static::<RoomTopicEventContent>(room_id)
|
||||||
|
.await?
|
||||||
|
.expect("room topic found before redaction")
|
||||||
|
.deserialize_as::<OriginalRoomTopicEvent>()
|
||||||
|
.expect("can deserialize room topic before redaction")
|
||||||
|
.content
|
||||||
|
.topic,
|
||||||
|
"😀"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut changes = StateChanges::default();
|
||||||
|
|
||||||
|
let redaction_json: &JsonValue = &test_json::TOPIC_REDACTION;
|
||||||
|
let redaction_evt = serde_json::from_value(redaction_json.clone()).expect("topic redaction event making works");
|
||||||
|
|
||||||
|
changes.add_redaction(room_id, redaction_evt);
|
||||||
|
store.save_changes(&changes).await?;
|
||||||
|
|
||||||
|
match store
|
||||||
|
.get_state_event_static::<RoomTopicEventContent>(room_id)
|
||||||
|
.await?
|
||||||
|
.expect("room topic found before redaction")
|
||||||
|
.deserialize_as::<OriginalRoomTopicEvent>()
|
||||||
|
{
|
||||||
|
Err(_) => { } // as expected
|
||||||
|
Ok(_) => panic!("Topic has not been redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = store
|
||||||
|
.get_state_event_static::<RoomTopicEventContent>(room_id)
|
||||||
|
.await?
|
||||||
|
.expect("room topic found after redaction")
|
||||||
|
.deserialize_as::<RedactedRoomTopicEvent>()
|
||||||
|
.expect("can deserialize room topic after redaction");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[async_test]
|
#[async_test]
|
||||||
async fn test_populate_store() -> StoreResult<()> {
|
async fn test_populate_store() -> StoreResult<()> {
|
||||||
let room_id = room_id();
|
let room_id = room_id();
|
||||||
|
|
|
@ -26,15 +26,8 @@ use dashmap::{DashMap, DashSet};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use matrix_sdk_common::{instant::Instant, locks::Mutex};
|
use matrix_sdk_common::{instant::Instant, locks::Mutex};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
|
||||||
use ruma::{
|
|
||||||
canonical_json::redact_in_place,
|
|
||||||
events::{
|
|
||||||
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
|
||||||
},
|
|
||||||
CanonicalJsonObject, RoomVersionId,
|
|
||||||
};
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
canonical_json::redact,
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
receipt::{Receipt, ReceiptType},
|
receipt::{Receipt, ReceiptType},
|
||||||
|
@ -43,20 +36,28 @@ use ruma::{
|
||||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
||||||
|
RoomVersionId, UserId,
|
||||||
};
|
};
|
||||||
use tracing::info;
|
#[cfg(feature = "experimental-timeline")]
|
||||||
|
use ruma::{
|
||||||
|
canonical_json::redact_in_place,
|
||||||
|
events::{
|
||||||
|
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
use super::BoxStream;
|
use super::BoxStream;
|
||||||
use super::{Result, RoomInfo, StateChanges, StateStore};
|
use super::{Result, RoomInfo, StateChanges, StateStore, StoreError};
|
||||||
|
#[cfg(feature = "experimental-timeline")]
|
||||||
|
use crate::deserialized_responses::SyncTimelineEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
deserialized_responses::MemberEvent,
|
deserialized_responses::MemberEvent,
|
||||||
media::{MediaRequest, UniqueKey},
|
media::{MediaRequest, UniqueKey},
|
||||||
MinimalRoomMemberEvent,
|
MinimalRoomMemberEvent,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
|
||||||
use crate::{deserialized_responses::SyncTimelineEvent, StoreError};
|
|
||||||
|
|
||||||
/// In-Memory, non-persistent implementation of the `StateStore`
|
/// In-Memory, non-persistent implementation of the `StateStore`
|
||||||
///
|
///
|
||||||
|
@ -353,10 +354,18 @@ impl MemoryStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let make_room_version = |room_id| {
|
||||||
|
self.room_info
|
||||||
|
.get(room_id)
|
||||||
|
.and_then(|info| info.room_version().cloned())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!(%room_id, "Unable to find the room version, assuming version 9");
|
||||||
|
RoomVersionId::V9
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
for (room_id, timeline) in &changes.timeline {
|
for (room_id, timeline) in &changes.timeline {
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
if timeline.sync {
|
if timeline.sync {
|
||||||
info!(%room_id, "Saving new timeline batch from sync response");
|
info!(%room_id, "Saving new timeline batch from sync response");
|
||||||
} else {
|
} else {
|
||||||
|
@ -407,16 +416,6 @@ impl MemoryStore {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let make_room_version = || {
|
|
||||||
self.room_info
|
|
||||||
.get(room_id)
|
|
||||||
.and_then(|info| info.room_version().cloned())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
warn!(%room_id, "Unable to find the room version, assuming version 9");
|
|
||||||
RoomVersionId::V9
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if timeline.sync {
|
if timeline.sync {
|
||||||
let mut room_version = None;
|
let mut room_version = None;
|
||||||
for event in &timeline.events {
|
for event in &timeline.events {
|
||||||
|
@ -433,7 +432,8 @@ impl MemoryStore {
|
||||||
if let Some(mut full_event) = data.events.get_mut(&position.clone()) {
|
if let Some(mut full_event) = data.events.get_mut(&position.clone()) {
|
||||||
let mut event_json: CanonicalJsonObject =
|
let mut event_json: CanonicalJsonObject =
|
||||||
full_event.event.deserialize_as()?;
|
full_event.event.deserialize_as()?;
|
||||||
let v = room_version.get_or_insert_with(make_room_version);
|
let v =
|
||||||
|
room_version.get_or_insert_with(|| make_room_version(room_id));
|
||||||
|
|
||||||
redact_in_place(&mut event_json, v)
|
redact_in_place(&mut event_json, v)
|
||||||
.map_err(StoreError::Redaction)?;
|
.map_err(StoreError::Redaction)?;
|
||||||
|
@ -463,6 +463,27 @@ impl MemoryStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (room_id, redactions) in &changes.redactions {
|
||||||
|
let mut room_version = None;
|
||||||
|
if let Some(room) = self.room_state.get_mut(room_id) {
|
||||||
|
for mut ref_room_mu in room.iter_mut() {
|
||||||
|
for mut ref_evt_mu in ref_room_mu.value_mut().iter_mut() {
|
||||||
|
let raw_evt = ref_evt_mu.value_mut();
|
||||||
|
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
|
||||||
|
if redactions.get(&event_id).is_some() {
|
||||||
|
let redacted = redact(
|
||||||
|
&raw_evt.deserialize_as::<CanonicalJsonObject>()?,
|
||||||
|
room_version.get_or_insert_with(|| make_room_version(room_id)),
|
||||||
|
)
|
||||||
|
.map_err(StoreError::Redaction)?;
|
||||||
|
*raw_evt = Raw::new(&redacted)?.cast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!("Saved changes in {:?}", now.elapsed());
|
info!("Saved changes in {:?}", now.elapsed());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -46,7 +46,10 @@ use ruma::{
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
receipt::{Receipt, ReceiptEventContent, ReceiptType},
|
receipt::{Receipt, ReceiptEventContent, ReceiptType},
|
||||||
room::member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
room::{
|
||||||
|
member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||||
|
redaction::OriginalSyncRoomRedactionEvent,
|
||||||
|
},
|
||||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||||
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent,
|
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent,
|
||||||
GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
|
GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
|
||||||
|
@ -686,6 +689,10 @@ pub struct StateChanges {
|
||||||
/// A map of `RoomId` to `ReceiptEventContent`.
|
/// A map of `RoomId` to `ReceiptEventContent`.
|
||||||
pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
|
pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
|
||||||
|
|
||||||
|
/// A map of `RoomId` to maps of `OwnedEventId` to be redacted by
|
||||||
|
/// `SyncRoomRedactionEvent`.
|
||||||
|
pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OriginalSyncRoomRedactionEvent>>,
|
||||||
|
|
||||||
/// A mapping of `RoomId` to a map of event type to a map of state key to
|
/// A mapping of `RoomId` to a map of event type to a map of state key to
|
||||||
/// `AnyStrippedStateEvent`.
|
/// `AnyStrippedStateEvent`.
|
||||||
pub stripped_state: BTreeMap<
|
pub stripped_state: BTreeMap<
|
||||||
|
@ -777,6 +784,14 @@ impl StateChanges {
|
||||||
.insert(event.state_key().to_owned(), raw_event);
|
.insert(event.state_key().to_owned(), raw_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Redact an event in the room
|
||||||
|
pub fn add_redaction(&mut self, room_id: &RoomId, redaction: OriginalSyncRoomRedactionEvent) {
|
||||||
|
self.redactions
|
||||||
|
.entry(room_id.to_owned())
|
||||||
|
.or_default()
|
||||||
|
.insert(redaction.redacts.clone(), redaction);
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the `StateChanges` struct with the given room with a new
|
/// Update the `StateChanges` struct with the given room with a new
|
||||||
/// `Notification`.
|
/// `Notification`.
|
||||||
pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) {
|
pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) {
|
||||||
|
|
|
@ -36,15 +36,8 @@ use matrix_sdk_base::{
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
||||||
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
|
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
|
||||||
use ruma::{
|
|
||||||
canonical_json::redact_in_place,
|
|
||||||
events::{
|
|
||||||
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
|
||||||
},
|
|
||||||
CanonicalJsonObject, RoomVersionId,
|
|
||||||
};
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
canonical_json::redact,
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
receipt::{Receipt, ReceiptType},
|
receipt::{Receipt, ReceiptType},
|
||||||
|
@ -53,11 +46,19 @@ use ruma::{
|
||||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId,
|
CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "experimental-timeline")]
|
||||||
|
use ruma::{
|
||||||
|
canonical_json::redact_in_place,
|
||||||
|
events::{
|
||||||
|
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
use tracing::{info, warn};
|
use tracing::info;
|
||||||
|
use tracing::warn;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::IdbKeyRange;
|
use web_sys::IdbKeyRange;
|
||||||
|
|
||||||
|
@ -582,6 +583,10 @@ impl IndexeddbStateStore {
|
||||||
stores.extend([KEYS::ROOM_STATE, KEYS::STRIPPED_ROOM_STATE]);
|
stores.extend([KEYS::ROOM_STATE, KEYS::STRIPPED_ROOM_STATE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !changes.redactions.is_empty() {
|
||||||
|
stores.extend([KEYS::ROOM_STATE, KEYS::ROOM_INFOS]);
|
||||||
|
}
|
||||||
|
|
||||||
if !changes.room_infos.is_empty() || !changes.stripped_room_infos.is_empty() {
|
if !changes.room_infos.is_empty() || !changes.stripped_room_infos.is_empty() {
|
||||||
stores.extend([KEYS::ROOM_INFOS, KEYS::STRIPPED_ROOM_INFOS]);
|
stores.extend([KEYS::ROOM_INFOS, KEYS::STRIPPED_ROOM_INFOS]);
|
||||||
}
|
}
|
||||||
|
@ -862,6 +867,53 @@ impl IndexeddbStateStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !changes.redactions.is_empty() {
|
||||||
|
let state = tx.object_store(KEYS::ROOM_STATE)?;
|
||||||
|
let room_info = tx.object_store(KEYS::ROOM_INFOS)?;
|
||||||
|
|
||||||
|
for (room_id, redactions) in &changes.redactions {
|
||||||
|
let range = self.encode_to_range(KEYS::ROOM_STATE, room_id)?;
|
||||||
|
let cursor = match state.open_cursor_with_range(&range)?.await? {
|
||||||
|
Some(c) => c,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut room_version = None;
|
||||||
|
|
||||||
|
while let Some(key) = cursor.key() {
|
||||||
|
let raw_evt =
|
||||||
|
self.deserialize_event::<Raw<AnySyncStateEvent>>(cursor.value())?;
|
||||||
|
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
|
||||||
|
if redactions.contains_key(&event_id) {
|
||||||
|
let version = {
|
||||||
|
if room_version.is_none() {
|
||||||
|
room_version.replace(room_info
|
||||||
|
.get(&self.encode_key(KEYS::ROOM_INFOS, room_id))?
|
||||||
|
.await?
|
||||||
|
.and_then(|f| self.deserialize_event::<RoomInfo>(f).ok())
|
||||||
|
.and_then(|info| info.room_version().cloned())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!(%room_id, "Unable to find the room version, assume version 9");
|
||||||
|
RoomVersionId::V9
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
room_version.as_ref().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let redacted =
|
||||||
|
redact(&raw_evt.deserialize_as::<CanonicalJsonObject>()?, version)
|
||||||
|
.map_err(StoreError::Redaction)?;
|
||||||
|
state.put_key_val(&key, &self.serialize_event(&redacted)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move forward.
|
||||||
|
cursor.advance(1)?.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
if !changes.timeline.is_empty() {
|
if !changes.timeline.is_empty() {
|
||||||
let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?;
|
let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?;
|
||||||
|
|
|
@ -34,15 +34,8 @@ use matrix_sdk_base::{
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream};
|
||||||
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
|
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
|
||||||
use ruma::{
|
|
||||||
canonical_json::redact_in_place,
|
|
||||||
events::{
|
|
||||||
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
|
||||||
},
|
|
||||||
CanonicalJsonObject, RoomVersionId,
|
|
||||||
};
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
canonical_json::redact,
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
receipt::{Receipt, ReceiptType},
|
receipt::{Receipt, ReceiptType},
|
||||||
|
@ -51,7 +44,15 @@ use ruma::{
|
||||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId,
|
CanonicalJsonObject, EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId,
|
||||||
|
RoomVersionId, UserId,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "experimental-timeline")]
|
||||||
|
use ruma::{
|
||||||
|
canonical_json::redact_in_place,
|
||||||
|
events::{
|
||||||
|
room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -61,7 +62,7 @@ use sled::{
|
||||||
Config, Db, Transactional, Tree,
|
Config, Db, Transactional, Tree,
|
||||||
};
|
};
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
#[cfg(feature = "crypto-store")]
|
#[cfg(feature = "crypto-store")]
|
||||||
use super::OpenStoreError;
|
use super::OpenStoreError;
|
||||||
|
@ -773,6 +774,54 @@ impl SledStateStore {
|
||||||
|
|
||||||
ret?;
|
ret?;
|
||||||
|
|
||||||
|
if !changes.redactions.is_empty() {
|
||||||
|
let mut redactions_found = false;
|
||||||
|
let mut redactions_batch = sled::Batch::default();
|
||||||
|
|
||||||
|
let make_room_version = |room_id| {
|
||||||
|
self.room_info
|
||||||
|
.get(&self.encode_key(ROOM_INFO, room_id))
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|r| self.deserialize_value::<RoomInfo>(&r))
|
||||||
|
.transpose()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|info| info.room_version().cloned())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!(%room_id, "Unable to find the room version, assume version 9");
|
||||||
|
RoomVersionId::V9
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
for (room_id, redactions) in &changes.redactions {
|
||||||
|
let key_prefix = self.encode_key(ROOM_STATE, room_id);
|
||||||
|
let mut room_version = None;
|
||||||
|
|
||||||
|
// iterate through all saved state events and check whether they are among the
|
||||||
|
// redacted, if so apply redaction and save that to the batch
|
||||||
|
// applied all at once after.
|
||||||
|
for (key, evt) in self.room_state.scan_prefix(key_prefix).filter_map(|r| r.ok()) {
|
||||||
|
let raw_evt = self.deserialize_value::<Raw<AnySyncStateEvent>>(&evt)?;
|
||||||
|
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
|
||||||
|
if redactions.contains_key(&event_id) {
|
||||||
|
let redacted = redact(
|
||||||
|
&raw_evt.deserialize_as::<CanonicalJsonObject>()?,
|
||||||
|
room_version.get_or_insert_with(|| make_room_version(room_id)),
|
||||||
|
)
|
||||||
|
.map_err(StoreError::Redaction)?;
|
||||||
|
redactions_found = true;
|
||||||
|
redactions_batch.insert(key, self.serialize_value(&redacted)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if redactions_found {
|
||||||
|
self.room_state.apply_batch(redactions_batch)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ret: Result<(), TransactionError<SledStoreError>> =
|
let ret: Result<(), TransactionError<SledStoreError>> =
|
||||||
(&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction(
|
(&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction(
|
||||||
|(room_user_receipts, room_event_receipts, presence)| {
|
|(room_user_receipts, room_event_receipts, presence)| {
|
||||||
|
@ -1414,8 +1463,6 @@ impl SledStateStore {
|
||||||
|
|
||||||
#[cfg(feature = "experimental-timeline")]
|
#[cfg(feature = "experimental-timeline")]
|
||||||
async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> {
|
async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> {
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
let mut timeline_batch = sled::Batch::default();
|
let mut timeline_batch = sled::Batch::default();
|
||||||
let mut event_id_to_position_batch = sled::Batch::default();
|
let mut event_id_to_position_batch = sled::Batch::default();
|
||||||
let mut timeline_metadata_batch = sled::Batch::default();
|
let mut timeline_metadata_batch = sled::Batch::default();
|
||||||
|
|
|
@ -1862,6 +1862,7 @@ impl Client {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
async fn send_inner<Request>(
|
async fn send_inner<Request>(
|
||||||
&self,
|
&self,
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
|
@ -13,4 +13,5 @@ matrix-sdk = { path = "../../crates/matrix-sdk" }
|
||||||
once_cell = "1.13.0"
|
once_cell = "1.13.0"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
tracing = "0.1.36"
|
||||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
use std::{collections::HashMap, option_env};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assign::assign;
|
||||||
|
use matrix_sdk::{
|
||||||
|
ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa},
|
||||||
|
store::make_store_config,
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use tempfile::{tempdir, TempDir};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
static USERS: Lazy<Mutex<HashMap<String, (Client, TempDir)>>> = Lazy::new(Mutex::default);
|
||||||
|
|
||||||
|
#[ctor::ctor]
|
||||||
|
fn init_logging() {
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with(tracing_subscriber::fmt::layer().with_test_writer())
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// read the test configuration from the environment
|
||||||
|
pub fn test_server_conf() -> (String, String) {
|
||||||
|
(
|
||||||
|
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(),
|
||||||
|
option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_client_for_user(username: String) -> Result<Client> {
|
||||||
|
let mut users = USERS.lock().await;
|
||||||
|
if let Some((client, _)) = users.get(&username) {
|
||||||
|
return Ok(client.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (homeserver_url, _domain_name) = test_server_conf();
|
||||||
|
|
||||||
|
let tmp_dir = tempdir()?;
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.user_agent("matrix-sdk-integation-tests")
|
||||||
|
.store_config(make_store_config(tmp_dir.path(), None)?)
|
||||||
|
.homeserver_url(homeserver_url)
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
// safe to assume we have not registered this user yet, but ignore if we did
|
||||||
|
|
||||||
|
if let Err(resp) = client.register(RegistrationRequest::new()).await {
|
||||||
|
// FIXME: do actually check the registration types...
|
||||||
|
if let Some(_response) = resp.uiaa_response() {
|
||||||
|
let request = assign!(RegistrationRequest::new(), {
|
||||||
|
username: Some(username.as_ref()),
|
||||||
|
password: Some(username.as_ref()),
|
||||||
|
|
||||||
|
auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())),
|
||||||
|
});
|
||||||
|
// we don't care if this failed, then we just try to login anyways
|
||||||
|
let _ = client.register(request).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.login_username(&username, &username).send().await?;
|
||||||
|
users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
|
@ -1,2 +1,5 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod helpers;
|
||||||
|
|
|
@ -1,70 +1,2 @@
|
||||||
use std::{collections::HashMap, option_env};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assign::assign;
|
|
||||||
use matrix_sdk::{
|
|
||||||
ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa},
|
|
||||||
store::make_store_config,
|
|
||||||
Client,
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use tempfile::{tempdir, TempDir};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
static USERS: Lazy<Mutex<HashMap<String, (Client, TempDir)>>> = Lazy::new(Mutex::default);
|
|
||||||
|
|
||||||
#[ctor::ctor]
|
|
||||||
fn init_logging() {
|
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
||||||
tracing_subscriber::registry()
|
|
||||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
|
||||||
.with(tracing_subscriber::fmt::layer().with_test_writer())
|
|
||||||
.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// read the test configuration from the environment
|
|
||||||
pub fn test_server_conf() -> (String, String) {
|
|
||||||
(
|
|
||||||
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(),
|
|
||||||
option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_client_for_user(username: String) -> Result<Client> {
|
|
||||||
let mut users = USERS.lock().await;
|
|
||||||
if let Some((client, _)) = users.get(&username) {
|
|
||||||
return Ok(client.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (homeserver_url, _domain_name) = test_server_conf();
|
|
||||||
|
|
||||||
let tmp_dir = tempdir()?;
|
|
||||||
|
|
||||||
let client = Client::builder()
|
|
||||||
.user_agent("matrix-sdk-integation-tests")
|
|
||||||
.store_config(make_store_config(tmp_dir.path(), None)?)
|
|
||||||
.homeserver_url(homeserver_url)
|
|
||||||
.build()
|
|
||||||
.await?;
|
|
||||||
// safe to assume we have not registered this user yet, but ignore if we did
|
|
||||||
|
|
||||||
if let Err(resp) = client.register(RegistrationRequest::new()).await {
|
|
||||||
// FIXME: do actually check the registration types...
|
|
||||||
if let Some(_response) = resp.uiaa_response() {
|
|
||||||
let request = assign!(RegistrationRequest::new(), {
|
|
||||||
username: Some(username.as_ref()),
|
|
||||||
password: Some(username.as_ref()),
|
|
||||||
|
|
||||||
auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())),
|
|
||||||
});
|
|
||||||
// we don't care if this failed, then we just try to login anyways
|
|
||||||
let _ = client.register(request).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.login_username(&username, &username).send().await?;
|
|
||||||
users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod invitations;
|
mod invitations;
|
||||||
|
mod redaction;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use matrix_sdk::{
|
||||||
room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
|
room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::get_client_for_user;
|
use crate::helpers::get_client_for_user;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
async fn test_invitation_details() -> Result<()> {
|
async fn test_invitation_details() -> Result<()> {
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use assign::assign;
|
||||||
|
use matrix_sdk::{
|
||||||
|
config::SyncSettings,
|
||||||
|
ruma::{
|
||||||
|
api::client::room::create_room::v3::Request as CreateRoomRequest,
|
||||||
|
events::{
|
||||||
|
room::name::{RoomNameEventContent, SyncRoomNameEvent},
|
||||||
|
StateEventType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::helpers::get_client_for_user;
|
||||||
|
|
||||||
|
async fn sync_once(client: &Client) -> Result<()> {
|
||||||
|
let settings = match client.sync_token().await {
|
||||||
|
Some(token) => SyncSettings::default().token(token),
|
||||||
|
None => SyncSettings::default(),
|
||||||
|
};
|
||||||
|
client.sync_once(settings).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
|
async fn test_redacting_name() -> Result<()> {
|
||||||
|
let tamatoa = get_client_for_user("tamatoa".to_owned()).await?;
|
||||||
|
// create a room
|
||||||
|
let request = assign!(CreateRoomRequest::new(), {
|
||||||
|
is_direct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let room_id = tamatoa.create_room(request).await?.room_id;
|
||||||
|
for _ in 0..=10 {
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
if tamatoa.get_joined_room(&room_id).is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let room = tamatoa.get_joined_room(&room_id).unwrap();
|
||||||
|
// let's send a specific state event
|
||||||
|
|
||||||
|
let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned()));
|
||||||
|
|
||||||
|
room.send_state_event(content).await?;
|
||||||
|
// sync up.
|
||||||
|
for _ in 0..=10 {
|
||||||
|
// we call sync up to ten times to give the server time to flush other
|
||||||
|
// messages over and send us the new state event
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
|
||||||
|
if room.name().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(room.name(), Some("Inappropriate text".to_owned()));
|
||||||
|
// check state event.
|
||||||
|
|
||||||
|
let raw_event =
|
||||||
|
room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found");
|
||||||
|
let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?;
|
||||||
|
assert!(
|
||||||
|
room_name_event.as_original().expect("event exists").content.name.is_some(),
|
||||||
|
"Event not found"
|
||||||
|
);
|
||||||
|
|
||||||
|
room.redact(room_name_event.event_id(), None, None).await?;
|
||||||
|
// sync up.
|
||||||
|
for _ in 0..=10 {
|
||||||
|
// we call sync up to ten times to give the server time to flush other
|
||||||
|
// messages over and send us the new state ev
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
|
||||||
|
if room.name().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_event =
|
||||||
|
room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found");
|
||||||
|
let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?;
|
||||||
|
// Name content has been redacted
|
||||||
|
assert!(
|
||||||
|
room_name_event.as_original().expect("event exists").content.name.is_none(),
|
||||||
|
"Event hasn't been redacted"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
|
async fn test_redacting_name_static() -> Result<()> {
|
||||||
|
let tamatoa = get_client_for_user("tamatoa".to_owned()).await?;
|
||||||
|
// create a room
|
||||||
|
let request = assign!(CreateRoomRequest::new(), {
|
||||||
|
is_direct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let room_id = tamatoa.create_room(request).await?.room_id;
|
||||||
|
for _ in 0..=10 {
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
if tamatoa.get_joined_room(&room_id).is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let room = tamatoa.get_joined_room(&room_id).unwrap();
|
||||||
|
|
||||||
|
// let's send a specific state event
|
||||||
|
let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned()));
|
||||||
|
|
||||||
|
room.send_state_event(content).await?;
|
||||||
|
// sync up.
|
||||||
|
for _ in 0..=10 {
|
||||||
|
// we call sync up to ten times to give the server time to flush other
|
||||||
|
// messages over and send us the new state event
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
|
||||||
|
if room.name().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check state event.
|
||||||
|
|
||||||
|
let room_name_event: SyncRoomNameEvent =
|
||||||
|
room.get_state_event_static().await?.expect("Room Name not found").deserialize()?;
|
||||||
|
assert!(
|
||||||
|
room_name_event.as_original().expect("event exists").content.name.is_some(),
|
||||||
|
"Event not found"
|
||||||
|
);
|
||||||
|
|
||||||
|
room.redact(room_name_event.event_id(), None, None).await?;
|
||||||
|
// we sync up.
|
||||||
|
for _ in 0..=10 {
|
||||||
|
// we call sync up to ten times to give the server time to flush other
|
||||||
|
// messages over and send us the new state ev
|
||||||
|
sync_once(&tamatoa).await?;
|
||||||
|
|
||||||
|
if room.name().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let room_name_event: SyncRoomNameEvent =
|
||||||
|
room.get_state_event_static().await?.expect("Room Name not found").deserialize()?;
|
||||||
|
// Name content has been redacted
|
||||||
|
assert!(
|
||||||
|
room_name_event.as_original().expect("event exists").content.name.is_none(),
|
||||||
|
"Event hasn't been redacted"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ pub use sync_events::{
|
||||||
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
|
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
|
||||||
MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION,
|
MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION,
|
||||||
READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG,
|
READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG,
|
||||||
TOPIC, TYPING,
|
TOPIC, TOPIC_REDACTION, TYPING,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An empty response.
|
/// An empty response.
|
||||||
|
|
|
@ -739,6 +739,7 @@ pub static TOPIC: Lazy<JsonValue> = Lazy::new(|| {
|
||||||
"origin_server_ts": 151957878,
|
"origin_server_ts": 151957878,
|
||||||
"sender": "@example:localhost",
|
"sender": "@example:localhost",
|
||||||
"state_key": "",
|
"state_key": "",
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"type": "m.room.topic",
|
"type": "m.room.topic",
|
||||||
"prev_content": {
|
"prev_content": {
|
||||||
"topic": "test"
|
"topic": "test"
|
||||||
|
@ -763,3 +764,18 @@ pub static TYPING: Lazy<JsonValue> = Lazy::new(|| {
|
||||||
"type": "m.typing"
|
"type": "m.typing"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub static TOPIC_REDACTION: Lazy<JsonValue> = Lazy::new(|| {
|
||||||
|
json!({
|
||||||
|
"content": {},
|
||||||
|
"redacts": "$151957878228ssqrJ:localhost",
|
||||||
|
"event_id": "$151957878228ssqrJ_REDACTION:localhost",
|
||||||
|
"origin_server_ts": 151957879,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"type": "m.room.redaction",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1392990,
|
||||||
|
"prev_sender": "@example:localhost",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue