Merge branch 'main' into feat-sdk-sliding-sync-cancellation-token

This commit is contained in:
Ivan Enderlin
2023-04-26 12:19:02 +02:00
65 changed files with 2090 additions and 2095 deletions

View File

@@ -93,16 +93,16 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest]
node-version: [14.0, 16.0, 18.0, 19.0]
node-version: [16.0, 18.0, 19.0, 20.0]
include:
- os: ubuntu-latest
os-name: 🐧
- os: macos-latest
os-name: 🍏
node-version: 18.0
node-version: 20.0
- node-version: 18.0
- node-version: 20.0
build-doc: true
steps:
@@ -174,7 +174,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.0
node-version: 20.0
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}

View File

@@ -25,7 +25,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- name: Load cache
uses: Swatinem/rust-cache@v2

View File

@@ -39,7 +39,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.0
node-version: 20.0
- name: Install NPM dependencies
working-directory: ${{ env.PKG_PATH }}

1170
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -26,14 +26,14 @@ async-stream = "0.3.3"
async-trait = "0.1.60"
base64 = "0.21.0"
byteorder = "1.4.3"
ctor = "0.1.26"
ctor = "0.2.0"
dashmap = "5.2.0"
eyeball = "0.4.0"
eyeball = "0.6.0"
eyeball-im = "0.2.0"
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
http = "0.2.6"
ruma = { git = "https://github.com/ruma/ruma", rev = "89e398fd062b4e763a3341fc7067428285d51d09", features = ["client-api-c"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "89e398fd062b4e763a3341fc7067428285d51d09" }
ruma = { git = "https://github.com/ruma/ruma", rev = "0143bd9b9f5dcfcaa835afb76f342c12f014f945", features = ["client-api-c", "compat-user-id"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "0143bd9b9f5dcfcaa835afb76f342c12f014f945" }
once_cell = "1.16.0"
serde = "1.0.151"
serde_html_form = "0.2.0"

View File

@@ -27,7 +27,8 @@ pub enum PkDecryptionError {
}
/// Error type for the decoding and storing of the backup key.
#[derive(Debug, Error)]
#[derive(Debug, Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum DecodeError {
/// An error happened while decoding the recovery key.
#[error(transparent)]
@@ -40,7 +41,7 @@ pub enum DecodeError {
/// Struct containing info about the way the backup key got derived from a
/// passphrase.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, uniffi::Record)]
pub struct PassphraseInfo {
/// The salt that was used during key derivation.
pub private_key_salt: String,

View File

@@ -3,6 +3,7 @@ use std::collections::HashMap;
use matrix_sdk_crypto::Device as InnerDevice;
/// An E2EE capable Matrix device.
#[derive(uniffi::Record)]
pub struct Device {
/// The device owner.
pub user_id: String,

View File

@@ -7,7 +7,8 @@ use matrix_sdk_crypto::{
use matrix_sdk_sqlite::OpenStoreError;
use ruma::{IdParseError, OwnedUserId};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum KeyImportError {
#[error(transparent)]
Export(#[from] KeyExportError),
@@ -17,7 +18,8 @@ pub enum KeyImportError {
Json(#[from] serde_json::Error),
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum SecretImportError {
#[error(transparent)]
CryptoStore(#[from] InnerStoreError),
@@ -25,7 +27,8 @@ pub enum SecretImportError {
Import(#[from] RustSecretImportError),
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum SignatureError {
#[error(transparent)]
Signature(#[from] InnerSignatureError),
@@ -55,7 +58,7 @@ pub enum CryptoStoreError {
Identifier(#[from] IdParseError),
}
#[derive(Debug)]
#[derive(Debug, uniffi::Error)]
pub enum DecryptionError {
Serialization { error: String },
Identifier { error: String },

View File

@@ -56,7 +56,7 @@ pub use verification::{
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
/// Struct collecting data that is important to migrate to the rust-sdk
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, Serialize, uniffi::Record)]
pub struct MigrationData {
/// The pickled version of the Olm Account
account: PickledAccount,
@@ -79,6 +79,7 @@ pub struct MigrationData {
}
/// Struct collecting data that is important to migrate sessions to the rust-sdk
#[derive(uniffi::Record)]
pub struct SessionMigrationData {
/// The user id that the data belongs to.
user_id: String,
@@ -100,7 +101,7 @@ pub struct SessionMigrationData {
///
/// Holds all the information that needs to be stored in a database to restore
/// an account.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
pub struct PickledAccount {
/// The user id of the account owner.
pub user_id: String,
@@ -118,7 +119,7 @@ pub struct PickledAccount {
///
/// Holds all the information that needs to be stored in a database to restore
/// a Session.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
pub struct PickledSession {
/// The pickle string holding the Olm Session.
pub pickle: String,
@@ -136,7 +137,7 @@ pub struct PickledSession {
///
/// Holds all the information that needs to be stored in a database to restore
/// an InboundGroupSession.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
pub struct PickledInboundGroupSession {
/// The pickle string holding the InboundGroupSession.
pub pickle: String,
@@ -157,7 +158,7 @@ pub struct PickledInboundGroupSession {
}
/// Error type for the migration process.
#[derive(thiserror::Error, Debug)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum MigrationError {
/// Generic catch all error variant.
#[error("error migrating database: {error_message}")]
@@ -188,15 +189,16 @@ impl From<anyhow::Error> for MigrationError {
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the migration.
#[uniffi::export]
pub fn migrate(
data: MigrationData,
path: &str,
path: String,
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> Result<(), MigrationError> {
let runtime = Runtime::new().context("initializing tokio runtime")?;
runtime.block_on(async move {
migrate_data(data, path, passphrase, progress_listener).await?;
migrate_data(data, &path, passphrase, progress_listener).await?;
Ok(())
})
}
@@ -350,14 +352,15 @@ async fn save_changes(
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the migration.
#[uniffi::export]
pub fn migrate_sessions(
data: SessionMigrationData,
path: &str,
path: String,
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> Result<(), MigrationError> {
let runtime = Runtime::new().context("initializing tokio runtime")?;
runtime.block_on(migrate_session_data(data, path, passphrase, progress_listener))?;
runtime.block_on(migrate_session_data(data, &path, passphrase, progress_listener))?;
Ok(())
}
@@ -497,9 +500,10 @@ fn collect_sessions(
/// * `passphrase` - The passphrase that should be used to encrypt the data at
/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store
/// and all its data will remain unencrypted.
#[uniffi::export]
pub fn migrate_room_settings(
room_settings: HashMap<String, RoomSettings>,
path: &str,
path: String,
passphrase: Option<String>,
) -> Result<(), MigrationError> {
let runtime = Runtime::new().context("initializing tokio runtime")?;
@@ -540,7 +544,7 @@ impl<T: Fn(i32, i32)> ProgressListener for T {
}
/// An encryption algorithm to be used to encrypt messages sent to a room.
#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Deserialize, Serialize, PartialEq, uniffi::Enum)]
pub enum EventEncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
@@ -572,6 +576,7 @@ impl TryFrom<RustEventEncryptionAlgorithm> for EventEncryptionAlgorithm {
}
/// Who can see a room's history.
#[derive(uniffi::Enum)]
pub enum HistoryVisibility {
/// Previous events are accessible to newly joined members from the point
/// they were invited onwards.
@@ -717,7 +722,7 @@ pub struct CrossSigningStatus {
/// A struct containing private cross signing keys that can be backed up or
/// uploaded to the secret store.
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, Serialize, uniffi::Record)]
pub struct CrossSigningKeyExport {
/// The seed of the master key encoded as unpadded base64.
pub master_key: Option<String>,
@@ -810,7 +815,7 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
}
/// Room encryption settings which are modified by state events or user options
#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[derive(Debug, PartialEq, Deserialize, Serialize, uniffi::Record)]
pub struct RoomSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EventEncryptionAlgorithm,
@@ -959,12 +964,15 @@ mod test {
let migration_data: MigrationData = serde_json::from_value(data)?;
let dir = tempdir()?;
let path =
dir.path().to_str().expect("Creating a string from the tempdir path should not fail");
let path = dir
.path()
.to_str()
.expect("Creating a string from the tempdir path should not fail")
.to_owned();
migrate(migration_data, path, None, Box::new(|_, _| {}))?;
migrate(migration_data, path.clone(), None, Box::new(|_, _| {}))?;
let machine = OlmMachine::new("@ganfra146:matrix.org", "DEWRCMENGS", path, None)?;
let machine = OlmMachine::new("@ganfra146:matrix.org", "DEWRCMENGS", &path, None)?;
assert_eq!(
machine.identity_keys()["ed25519"],

View File

@@ -41,6 +41,7 @@ pub struct LoggerWrapper {
}
/// Set the logger that should be used to forward Rust logs over FFI.
#[uniffi::export]
pub fn set_logger(logger: Box<dyn Logger>) {
let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) };

View File

@@ -79,6 +79,7 @@ impl Drop for OlmMachine {
/// A pair of outgoing room key requests, both of those are sendToDevice
/// requests.
#[derive(uniffi::Record)]
pub struct KeyRequestPair {
/// The optional cancellation, this is None if no previous key request was
/// sent out for this key, thus it doesn't need to be cancelled.
@@ -88,7 +89,7 @@ pub struct KeyRequestPair {
}
/// The result of a signature verification of a signed JSON object.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
pub struct SignatureVerification {
/// The result of the signature verification using the public key of our own
/// device.
@@ -127,36 +128,6 @@ impl From<RustSignatureCheckResult> for SignatureVerification {
}
}
#[uniffi::export]
impl OlmMachine {
/// Get the user ID of the owner of this `OlmMachine`.
pub fn user_id(&self) -> String {
self.inner.user_id().to_string()
}
/// Get the device ID of the device of this `OlmMachine`.
pub fn device_id(&self) -> String {
self.inner.device_id().to_string()
}
/// Get our own identity keys.
pub fn identity_keys(&self) -> HashMap<String, String> {
let identity_keys = self.inner.identity_keys();
let curve_key = identity_keys.curve25519.to_base64();
let ed25519_key = identity_keys.ed25519.to_base64();
HashMap::from([("ed25519".to_owned(), ed25519_key), ("curve25519".to_owned(), curve_key)])
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we have
/// stored locally.
pub fn cross_signing_status(&self) -> CrossSigningStatus {
self.runtime.block_on(self.inner.cross_signing_status()).into()
}
}
impl OlmMachine {
/// Create a new `OlmMachine`
///
@@ -192,9 +163,63 @@ impl OlmMachine {
Ok(OlmMachine { inner: ManuallyDrop::new(inner), runtime })
}
/// Get the display name of our own device.
pub fn display_name(&self) -> Result<Option<String>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.display_name())?)
fn import_room_keys_helper(
&self,
keys: Vec<ExportedRoomKey>,
from_backup: bool,
progress_listener: Box<dyn ProgressListener>,
) -> Result<KeysImportResult, KeyImportError> {
let listener = |progress: usize, total: usize| {
progress_listener.on_progress(progress as i32, total as i32)
};
let result =
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
Ok(KeysImportResult {
imported: result.imported_count as i64,
total: result.total_count as i64,
keys: result
.keys
.into_iter()
.map(|(r, m)| {
(
r.to_string(),
m.into_iter().map(|(s, k)| (s, k.into_iter().collect())).collect(),
)
})
.collect(),
})
}
}
#[uniffi::export]
impl OlmMachine {
/// Get the user ID of the owner of this `OlmMachine`.
pub fn user_id(&self) -> String {
self.inner.user_id().to_string()
}
/// Get the device ID of the device of this `OlmMachine`.
pub fn device_id(&self) -> String {
self.inner.device_id().to_string()
}
/// Get our own identity keys.
pub fn identity_keys(&self) -> HashMap<String, String> {
let identity_keys = self.inner.identity_keys();
let curve_key = identity_keys.curve25519.to_base64();
let ed25519_key = identity_keys.ed25519.to_base64();
HashMap::from([("ed25519".to_owned(), ed25519_key), ("curve25519".to_owned(), curve_key)])
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we have
/// stored locally.
pub fn cross_signing_status(&self) -> CrossSigningStatus {
self.runtime.block_on(self.inner.cross_signing_status()).into()
}
/// Get a cross signing user identity for the given user ID.
@@ -211,10 +236,10 @@ impl OlmMachine {
/// received.
pub fn get_identity(
&self,
user_id: &str,
user_id: String,
timeout: u32,
) -> Result<Option<UserIdentity>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let user_id = parse_user_id(&user_id)?;
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
@@ -230,8 +255,8 @@ impl OlmMachine {
}
/// Check if a user identity is considered to be verified by us.
pub fn is_identity_verified(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
pub fn is_identity_verified(&self, user_id: String) -> Result<bool, CryptoStoreError> {
let user_id = parse_user_id(&user_id)?;
Ok(
if let Some(identity) =
@@ -258,7 +283,10 @@ impl OlmMachine {
///
/// Returns a request that needs to be sent out for the user identity to be
/// marked as verified.
pub fn verify_identity(&self, user_id: &str) -> Result<SignatureUploadRequest, SignatureError> {
pub fn verify_identity(
&self,
user_id: String,
) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::parse(user_id)?;
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
@@ -290,17 +318,17 @@ impl OlmMachine {
/// received.
pub fn get_device(
&self,
user_id: &str,
device_id: &str,
user_id: String,
device_id: String,
timeout: u32,
) -> Result<Option<Device>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let user_id = parse_user_id(&user_id)?;
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
Ok(self
.runtime
.block_on(self.inner.get_device(&user_id, device_id.into(), timeout))?
.block_on(self.inner.get_device(&user_id, device_id.as_str().into(), timeout))?
.map(|d| d.into()))
}
@@ -319,17 +347,20 @@ impl OlmMachine {
/// as verified.
pub fn verify_device(
&self,
user_id: &str,
device_id: &str,
user_id: String,
device_id: String,
) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::parse(user_id)?;
let device =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
let device = self.runtime.block_on(self.inner.get_device(
&user_id,
device_id.as_str().into(),
None,
))?;
if let Some(device) = device {
Ok(self.runtime.block_on(device.verify())?.into())
} else {
Err(SignatureError::UnknownDevice(user_id, device_id.to_owned()))
Err(SignatureError::UnknownDevice(user_id, device_id))
}
}
@@ -337,14 +368,17 @@ impl OlmMachine {
/// or uploading any signatures if verified
pub fn set_local_trust(
&self,
user_id: &str,
device_id: &str,
user_id: String,
device_id: String,
trust_state: LocalTrust,
) -> Result<(), CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let user_id = parse_user_id(&user_id)?;
let device =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
let device = self.runtime.block_on(self.inner.get_device(
&user_id,
device_id.as_str().into(),
None,
))?;
if let Some(device) = device {
self.runtime.block_on(device.set_local_trust(trust_state))?;
@@ -367,10 +401,10 @@ impl OlmMachine {
/// received.
pub fn get_user_devices(
&self,
user_id: &str,
user_id: String,
timeout: u32,
) -> Result<Vec<Device>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let user_id = parse_user_id(&user_id)?;
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
Ok(self
@@ -410,13 +444,13 @@ impl OlmMachine {
/// * `response_body` - The body of the response that was received.
pub fn mark_request_as_sent(
&self,
request_id: &str,
request_id: String,
request_type: RequestType,
response_body: &str,
response_body: String,
) -> Result<(), CryptoStoreError> {
let id: OwnedTransactionId = request_id.into();
let response = response_from_string(response_body);
let response = response_from_string(&response_body);
let response: OwnedResponse = match request_type {
RequestType::KeysUpload => {
@@ -447,10 +481,7 @@ impl OlmMachine {
Ok(())
}
}
#[uniffi::export]
impl OlmMachine {
/// Let the state machine know about E2EE related sync changes that we
/// received from the server.
///
@@ -867,37 +898,6 @@ impl OlmMachine {
Ok(encrypted)
}
}
impl OlmMachine {
fn import_room_keys_helper(
&self,
keys: Vec<ExportedRoomKey>,
from_backup: bool,
progress_listener: Box<dyn ProgressListener>,
) -> Result<KeysImportResult, KeyImportError> {
let listener = |progress: usize, total: usize| {
progress_listener.on_progress(progress as i32, total as i32)
};
let result =
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
Ok(KeysImportResult {
imported: result.imported_count as i64,
total: result.total_count as i64,
keys: result
.keys
.into_iter()
.map(|(r, m)| {
(
r.to_string(),
m.into_iter().map(|(s, k)| (s, k.into_iter().collect())).collect(),
)
})
.collect(),
})
}
/// Import room keys from the given serialized key export.
///
@@ -911,12 +911,12 @@ impl OlmMachine {
/// progress of the key import.
pub fn import_room_keys(
&self,
keys: &str,
passphrase: &str,
keys: String,
passphrase: String,
progress_listener: Box<dyn ProgressListener>,
) -> Result<KeysImportResult, KeyImportError> {
let keys = Cursor::new(keys);
let keys = decrypt_room_key_export(keys, passphrase)?;
let keys = decrypt_room_key_export(keys, &passphrase)?;
self.import_room_keys_helper(keys, false, progress_listener)
}
@@ -935,19 +935,16 @@ impl OlmMachine {
/// progress of the key import.
pub fn import_decrypted_room_keys(
&self,
keys: &str,
keys: String,
progress_listener: Box<dyn ProgressListener>,
) -> Result<KeysImportResult, KeyImportError> {
let keys: Vec<Value> = serde_json::from_str(keys)?;
let keys: Vec<Value> = serde_json::from_str(&keys)?;
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
self.import_room_keys_helper(keys, true, progress_listener)
}
}
#[uniffi::export]
impl OlmMachine {
/// Discard the currently active room key for the given room if there is
/// one.
pub fn discard_room_key(&self, room_id: String) -> Result<(), CryptoStoreError> {
@@ -1352,14 +1349,12 @@ impl OlmMachine {
.ok()
.map(Arc::new))
}
}
impl OlmMachine {
/// Sign the given message using our device key and if available cross
/// signing master key.
pub fn sign(&self, message: &str) -> HashMap<String, HashMap<String, String>> {
pub fn sign(&self, message: String) -> HashMap<String, HashMap<String, String>> {
self.runtime
.block_on(self.inner.sign(message))
.block_on(self.inner.sign(&message))
.into_iter()
.map(|(k, v)| {
(
@@ -1397,9 +1392,9 @@ impl OlmMachine {
/// ```
pub fn verify_backup(
&self,
backup_info: &str,
backup_info: String,
) -> Result<SignatureVerification, CryptoStoreError> {
let backup_info = serde_json::from_str(backup_info)?;
let backup_info = serde_json::from_str(&backup_info)?;
Ok(self
.runtime

View File

@@ -1,31 +1,4 @@
namespace matrix_sdk_crypto_ffi {
void set_logger(Logger logger);
[Throws=MigrationError]
void migrate(
MigrationData data,
[ByRef] string path,
string? passphrase,
ProgressListener progress_listener
);
[Throws=MigrationError]
void migrate_sessions(
SessionMigrationData data,
[ByRef] string path,
string? passphrase,
ProgressListener progress_listener
);
[Throws=MigrationError]
void migrate_room_settings(
record<string, RoomSettings> room_settings,
[ByRef] string path,
string? passphrase
);
};
[Error]
interface MigrationError {
Generic(string error_message);
};
namespace matrix_sdk_crypto_ffi {};
callback interface Logger {
void log(string log_line);
@@ -35,29 +8,6 @@ callback interface ProgressListener {
void on_progress(i32 progress, i32 total);
};
[Error]
enum KeyImportError {
"Export",
"CryptoStore",
"Json",
};
[Error]
enum SignatureError {
"Signature",
"Identifier",
"CryptoStore",
"UnknownDevice",
"UnknownUserIdentity",
};
[Error]
enum SecretImportError {
"Import",
"CryptoStore",
};
[Error]
enum CryptoStoreError {
"OpenStore",
@@ -68,96 +18,12 @@ enum CryptoStoreError {
"Identifier",
};
[Error]
interface DecryptionError {
Identifier(string error);
Serialization(string error);
Megolm(string error);
MissingRoomKey(string error, string? withheld_code);
Store(string error);
};
dictionary KeysImportResult {
i64 imported;
i64 total;
record<DOMString, record<DOMString, sequence<string>>> keys;
};
dictionary Device {
string user_id;
string device_id;
record<DOMString, string> keys;
sequence<string> algorithms;
string? display_name;
boolean is_blocked;
boolean locally_trusted;
boolean cross_signing_trusted;
};
[Enum]
interface UserIdentity {
Own(
string user_id,
boolean trusts_our_own_device,
string master_key,
string self_signing_key,
string user_signing_key
);
Other(
string user_id,
string master_key,
string self_signing_key
);
};
dictionary CrossSigningKeyExport {
string? master_key;
string? self_signing_key;
string? user_signing_key;
};
dictionary UploadSigningKeysRequest {
string master_key;
string self_signing_key;
string user_signing_key;
};
dictionary BootstrapCrossSigningResult {
UploadSigningKeysRequest upload_signing_keys_request;
SignatureUploadRequest signature_request;
};
dictionary CancelInfo {
string cancel_code;
string reason;
boolean cancelled_by_us;
};
dictionary StartSasResult {
Sas sas;
OutgoingVerificationRequest request;
};
interface Sas {
string other_user_id();
string other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_done();
OutgoingVerificationRequest? accept();
[Throws=CryptoStoreError]
ConfirmVerificationResult? confirm();
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
sequence<i32>? get_emoji_indices();
sequence<i32>? get_decimals();
void set_changes_listener(SasListener listener);
SasState state();
};
[Enum]
interface SasState {
Started();
@@ -172,32 +38,6 @@ callback interface SasListener {
void on_change(SasState state);
};
dictionary ScanResult {
QrCode qr;
OutgoingVerificationRequest request;
};
interface QrCode {
string other_user_id();
string other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_done();
boolean is_cancelled();
CancelInfo? cancel_info();
boolean reciprocated();
boolean has_been_scanned();
ConfirmVerificationResult? confirm();
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
string? generate_qr_code();
void set_changes_listener(QrCodeListener listener);
QrCodeState state();
};
[Enum]
interface QrCodeState {
Started();
@@ -212,36 +52,6 @@ callback interface QrCodeListener {
void on_change(QrCodeState state);
};
interface VerificationRequest {
string other_user_id();
string? other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_ready();
boolean is_done();
boolean is_passive();
boolean is_cancelled();
CancelInfo? cancel_info();
sequence<string>? their_supported_methods();
sequence<string>? our_supported_methods();
OutgoingVerificationRequest? accept(sequence<string> methods);
[Throws=CryptoStoreError]
StartSasResult? start_sas_verification();
[Throws=CryptoStoreError]
QrCode? start_qr_verification();
ScanResult? scan_qr_code([ByRef] string data);
OutgoingVerificationRequest? cancel();
void set_changes_listener(VerificationRequestListener listener);
VerificationRequestState state();
};
[Enum]
interface VerificationRequestState {
Requested();
@@ -254,57 +64,6 @@ callback interface VerificationRequestListener {
void on_change(VerificationRequestState state);
};
dictionary RequestVerificationResult {
VerificationRequest verification;
OutgoingVerificationRequest request;
};
dictionary ConfirmVerificationResult {
sequence<OutgoingVerificationRequest> requests;
SignatureUploadRequest? signature_request;
};
interface Verification {
QrCode? as_qr();
Sas? as_sas();
};
dictionary KeyRequestPair {
Request? cancellation;
Request key_request;
};
[Enum]
interface OutgoingVerificationRequest {
ToDevice(string request_id, string event_type, string body);
InRoom(string request_id, string room_id, string event_type, string content);
};
[Enum]
interface Request {
ToDevice(string request_id, string event_type, string body);
KeysUpload(string request_id, string body);
KeysQuery(string request_id, sequence<string> users);
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
KeysBackup(string request_id, string version, string rooms);
RoomMessage(string request_id, string room_id, string event_type, string content);
SignatureUpload(string request_id, string body);
};
dictionary SignatureUploadRequest {
string body;
};
enum RequestType {
"KeysQuery",
"KeysClaim",
"KeysUpload",
"ToDevice",
"SignatureUpload",
"KeysBackup",
"RoomMessage",
};
enum LocalTrust {
"Verified",
"BlackListed",
@@ -312,19 +71,6 @@ enum LocalTrust {
"Unset",
};
enum EventEncryptionAlgorithm {
"OlmV1Curve25519AesSha2",
"MegolmV1AesSha2",
};
enum HistoryVisibility {
"Invited",
"Joined",
"Shared",
"WorldReadable",
};
interface OlmMachine {
[Throws=CryptoStoreError]
constructor(
@@ -333,59 +79,6 @@ interface OlmMachine {
[ByRef] string path,
string? passphrase
);
[Throws=CryptoStoreError]
sequence<Request> outgoing_requests();
[Throws=CryptoStoreError]
void mark_request_as_sent(
[ByRef] string request_id,
RequestType request_type,
[ByRef] string response
);
[Throws=CryptoStoreError]
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
[Throws=SignatureError]
SignatureUploadRequest verify_identity([ByRef] string user_id);
[Throws=CryptoStoreError]
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
[Throws=CryptoStoreError]
void set_local_trust([ByRef] string user_id, [ByRef] string device_id, LocalTrust trust_state);
[Throws=SignatureError]
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
[Throws=KeyImportError]
KeysImportResult import_room_keys(
[ByRef] string keys,
[ByRef] string passphrase,
ProgressListener progress_listener
);
[Throws=KeyImportError]
KeysImportResult import_decrypted_room_keys(
[ByRef] string keys,
ProgressListener progress_listener
);
[Throws=CryptoStoreError]
boolean is_identity_verified([ByRef] string user_id);
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
[Throws=CryptoStoreError]
SignatureVerification verify_backup([ByRef] string auth_data);
};
dictionary PassphraseInfo {
string private_key_salt;
i32 private_key_iterations;
};
dictionary SignatureVerification {
SignatureState device_signature;
SignatureState user_identity_signature;
record<DOMString, SignatureState> other_devices_signatures;
boolean trusted;
};
enum SignatureState {
@@ -395,12 +88,6 @@ enum SignatureState {
"ValidAndTrusted",
};
[Error]
enum DecodeError {
"Decode",
"CryptoStore",
};
interface BackupRecoveryKey {
constructor();
[Name=from_passphrase]
@@ -412,56 +99,3 @@ interface BackupRecoveryKey {
[Name=from_base58, Throws=DecodeError]
constructor(string key);
};
dictionary MigrationData {
PickledAccount account;
sequence<PickledSession> sessions;
sequence<PickledInboundGroupSession> inbound_group_sessions;
string? backup_version;
string? backup_recovery_key;
sequence<u8> pickle_key;
CrossSigningKeyExport cross_signing;
sequence<string> tracked_users;
record<string, RoomSettings> room_settings;
};
dictionary SessionMigrationData {
string user_id;
string device_id;
string curve25519_key;
string ed25519_key;
sequence<PickledSession> sessions;
sequence<PickledInboundGroupSession> inbound_group_sessions;
sequence<u8> pickle_key;
};
dictionary PickledAccount {
string user_id;
string device_id;
string pickle;
boolean shared;
i64 uploaded_signed_key_count;
};
dictionary PickledSession {
string pickle;
string sender_key;
boolean created_using_fallback_key;
string creation_time;
string last_use_time;
};
dictionary PickledInboundGroupSession {
string pickle;
string sender_key;
record<DOMString, string> signing_key;
string room_id;
sequence<string> forwarding_chains;
boolean imported;
boolean backed_up;
};
dictionary RoomSettings {
EventEncryptionAlgorithm algorithm;
boolean only_allow_trusted_devices;
};

View File

@@ -28,6 +28,7 @@ use ruma::{
};
use serde_json::json;
#[derive(uniffi::Record)]
pub struct SignatureUploadRequest {
pub body: String,
}
@@ -41,6 +42,7 @@ impl From<RustSignatureUploadRequest> for SignatureUploadRequest {
}
}
#[derive(uniffi::Record)]
pub struct UploadSigningKeysRequest {
pub master_key: String,
pub self_signing_key: String,
@@ -66,6 +68,7 @@ impl From<RustUploadSigningKeysRequest> for UploadSigningKeysRequest {
}
}
#[derive(uniffi::Record)]
pub struct BootstrapCrossSigningResult {
pub upload_signing_keys_request: UploadSigningKeysRequest,
pub signature_request: SignatureUploadRequest,
@@ -82,6 +85,7 @@ impl From<(RustUploadSigningKeysRequest, RustSignatureUploadRequest)>
}
}
#[derive(uniffi::Enum)]
pub enum OutgoingVerificationRequest {
ToDevice { request_id: String, event_type: String, body: String },
InRoom { request_id: String, room_id: String, event_type: String, content: String },
@@ -112,7 +116,7 @@ impl From<ToDeviceRequest> for OutgoingVerificationRequest {
}
}
#[derive(Debug)]
#[derive(Debug, uniffi::Enum)]
pub enum Request {
ToDevice { request_id: String, event_type: String, body: String },
KeysUpload { request_id: String, body: String },
@@ -221,6 +225,7 @@ pub(crate) fn response_from_string(body: &str) -> Response<Vec<u8>> {
.expect("Can't create HTTP response")
}
#[derive(uniffi::Enum)]
pub enum RequestType {
KeysQuery,
KeysClaim,
@@ -254,6 +259,7 @@ impl From<DeviceLists> for RumaDeviceLists {
}
}
#[derive(uniffi::Record)]
pub struct KeysImportResult {
/// The number of room keys that were imported.
pub imported: i64,

View File

@@ -4,6 +4,7 @@ use crate::CryptoStoreError;
/// Enum representing cross signing identities of our own user or some other
/// user.
#[derive(uniffi::Enum)]
pub enum UserIdentity {
/// Our own user identity.
Own {

View File

@@ -77,11 +77,13 @@ impl From<RustSasState> for SasState {
}
/// Enum representing the different verification flows we support.
#[derive(uniffi::Object)]
pub struct Verification {
pub(crate) inner: InnerVerification,
pub(crate) runtime: Handle,
}
#[uniffi::export]
impl Verification {
/// Try to represent the `Verification` as an `Sas` verification object,
/// returns `None` if the verification is not a `Sas` verification.
@@ -105,11 +107,13 @@ impl Verification {
}
/// The `m.sas.v1` verification flow.
#[derive(uniffi::Object)]
pub struct Sas {
pub(crate) inner: InnerSas,
pub(crate) runtime: Handle,
}
#[uniffi::export]
impl Sas {
/// Get the user id of the other side.
pub fn other_user_id(&self) -> String {
@@ -170,7 +174,7 @@ impl Sas {
/// list of cancel codes can be found in the [spec]
///
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
}
@@ -248,7 +252,9 @@ impl Sas {
pub fn state(&self) -> SasState {
self.inner.state().into()
}
}
impl Sas {
async fn changes_listener(
mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
listener: Box<dyn SasListener>,
@@ -315,11 +321,13 @@ impl From<QrVerificationState> for QrCodeState {
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
/// verification flow.
#[derive(uniffi::Object)]
pub struct QrCode {
pub(crate) inner: InnerQr,
pub(crate) runtime: Handle,
}
#[uniffi::export]
impl QrCode {
/// Get the user id of the other side.
pub fn other_user_id(&self) -> String {
@@ -386,7 +394,7 @@ impl QrCode {
/// list of cancel codes can be found in the [spec]
///
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
}
@@ -424,7 +432,9 @@ impl QrCode {
pub fn state(&self) -> QrCodeState {
self.inner.state().into()
}
}
impl QrCode {
async fn changes_listener(
mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
listener: Box<dyn QrCodeListener>,
@@ -468,6 +478,7 @@ impl From<RustCancelInfo> for CancelInfo {
}
/// A result type for starting SAS verifications.
#[derive(uniffi::Record)]
pub struct StartSasResult {
/// The SAS verification object that got created.
pub sas: Arc<Sas>,
@@ -477,6 +488,7 @@ pub struct StartSasResult {
}
/// A result type for scanning QR codes.
#[derive(uniffi::Record)]
pub struct ScanResult {
/// The QR code verification object that got created.
pub qr: Arc<QrCode>,
@@ -486,6 +498,7 @@ pub struct ScanResult {
}
/// A result type for requesting verifications.
#[derive(uniffi::Record)]
pub struct RequestVerificationResult {
/// The verification request object that got created.
pub verification: Arc<VerificationRequest>,
@@ -495,6 +508,7 @@ pub struct RequestVerificationResult {
}
/// A result type for confirming verifications.
#[derive(uniffi::Record)]
pub struct ConfirmVerificationResult {
/// The requests that needs to be sent out to notify the other side that we
/// confirmed the verification.
@@ -558,11 +572,13 @@ impl From<RustVerificationRequestState> for VerificationRequestState {
/// The verificatoin request object which then can transition into some concrete
/// verification method
#[derive(uniffi::Object)]
pub struct VerificationRequest {
pub(crate) inner: InnerVerificationRequest,
pub(crate) runtime: Handle,
}
#[uniffi::export]
impl VerificationRequest {
/// The id of the other user that is participating in this verification
/// request.
@@ -709,7 +725,7 @@ impl VerificationRequest {
///
/// * `data` - The data that was extracted from the scanned QR code as an
/// base64 encoded string, without padding.
pub fn scan_qr_code(&self, data: &str) -> Option<ScanResult> {
pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
let data = STANDARD_NO_PAD.decode(data).ok()?;
let data = QrVerificationData::from_bytes(data).ok()?;
@@ -738,7 +754,9 @@ impl VerificationRequest {
pub fn state(&self) -> VerificationRequestState {
self.inner.state().into()
}
}
impl VerificationRequest {
async fn changes_listener(
mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
listener: Box<dyn VerificationRequestListener>,

View File

@@ -96,8 +96,8 @@ according to [the Node.js Releases
Page](https://nodejs.org/en/about/releases/), _and_ which are
compatible with [NAPI v6 (Node.js
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
means that this binding will work with the following versions: 14.0.0,
16.0.0 and 18.0.0.
means that this binding will work with the following versions: 16.0.0,
18.0.0, 19.0.0 and 20.0.0.
Once the Rust compiler, Node.js and npm are installed, you can run the
following commands:

View File

@@ -20,7 +20,7 @@
"yargs-parser": "~21.0.1"
},
"engines": {
"node": ">= 14"
"node": ">= 16"
},
"scripts": {
"lint": "prettier --check .",

View File

@@ -1,7 +1,7 @@
use std::time::Duration;
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use napi::bindgen_prelude::{BigInt, ToNapiValue};
use napi::bindgen_prelude::{BigInt, FromNapiValue, ToNapiValue};
use napi_derive::*;
use crate::events;

View File

@@ -1,6 +1,6 @@
//! Types related to events.
use napi::bindgen_prelude::ToNapiValue;
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;

View File

@@ -1,7 +1,7 @@
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
//! events, keys, rooms, servers, users and URIs.
use napi::bindgen_prelude::ToNapiValue;
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
use napi_derive::*;
use crate::into_err;

View File

@@ -7,7 +7,7 @@ use std::{
sync::Arc,
};
use napi::bindgen_prelude::{within_runtime_if_available, Either7, ToNapiValue};
use napi::bindgen_prelude::{within_runtime_if_available, Either7, FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{value::RawValue, Value as JsonValue};

View File

@@ -6,7 +6,7 @@ use matrix_sdk_crypto::requests::{
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
};
use napi::bindgen_prelude::{Either7, ToNapiValue};
use napi::bindgen_prelude::{Either7, FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::{
api::client::keys::{

View File

@@ -1,8 +1,5 @@
namespace matrix_sdk_ffi {};
interface TaskHandle {};
[Error]
interface ClientError {
Generic(string msg);
@@ -12,16 +9,6 @@ callback interface ClientDelegate {
void did_receive_auth_error(boolean is_soft_logout);
};
dictionary RequiredState {
string key;
string value;
};
dictionary RoomSubscription {
sequence<RequiredState>? required_state;
u32? timeline_limit;
};
dictionary UpdateSummary {
sequence<string> lists;
sequence<string> rooms;
@@ -93,59 +80,10 @@ callback interface SlidingSyncListRoomItemsObserver {
interface SlidingSyncListBuilder {
constructor();
[Self=ByArc]
SlidingSyncListBuilder sync_mode(SlidingSyncMode mode);
[Throws=ClientError, Self=ByArc]
SlidingSyncList build();
};
interface SlidingSyncList {
TaskHandle observe_room_list(SlidingSyncListRoomListObserver observer);
TaskHandle observe_rooms_count(SlidingSyncListRoomsCountObserver observer);
TaskHandle observe_state(SlidingSyncListStateObserver observer);
};
interface SlidingSyncRoom {
[Throws=ClientError]
SlidingSyncSubscribeResult subscribe_and_add_timeline_listener(TimelineListener listener, RoomSubscription? settings);
[Throws=ClientError]
SlidingSyncSubscribeResult add_timeline_listener(TimelineListener listener);
};
dictionary SlidingSyncSubscribeResult {
sequence<TimelineItem> items;
TaskHandle task_handle;
};
interface SlidingSync {
void set_observer(SlidingSyncObserver? observer);
[Throws=ClientError]
void subscribe(string room_id, RoomSubscription? settings);
[Throws=ClientError]
void unsubscribe(string room_id);
[Throws=ClientError]
SlidingSyncRoom? get_room(string room_id);
[Throws=ClientError]
sequence<SlidingSyncRoom?> get_rooms(sequence<string> room_ids);
};
interface ClientBuilder {
constructor();
[Throws=ClientError, Self=ByArc]
Client build();
};
interface SlidingSyncBuilder {
[Throws=ClientError, Self=ByArc]
SlidingSyncBuilder homeserver(string url);
[Throws=ClientError, Self=ByArc]
SlidingSync build();
};
dictionary CreateRoomParameters {
@@ -181,107 +119,8 @@ enum RoomPreset {
"TrustedPrivateChat",
};
interface Client {
void set_delegate(ClientDelegate? delegate);
[Throws=ClientError]
void login(string username, string password, string? initial_device_name, string? device_id);
[Throws=ClientError]
MediaFileHandle get_media_file(MediaSource source, string mime_type);
};
interface MediaFileHandle {
string path();
};
enum MembershipState {
/// The user is banned.
"Ban",
/// The user has been invited.
"Invite",
/// The user has joined.
"Join",
/// The user has requested to join.
"Knock",
/// The user has left.
"Leave",
};
interface RoomMember { };
interface Room {
[Throws=ClientError]
string display_name();
[Throws=ClientError]
boolean is_encrypted();
[Throws=ClientError]
sequence<RoomMember> members();
[Throws=ClientError]
string? member_avatar_url(string user_id);
[Throws=ClientError]
string? member_display_name(string user_id);
sequence<TimelineItem> add_timeline_listener(TimelineListener listener);
// Loads older messages into the timeline.
//
// Raises an exception if there are no timeline listeners.
[Throws=ClientError]
void paginate_backwards(PaginationOptions opts);
[Throws=ClientError]
void send_read_receipt(string event_id);
[Throws=ClientError]
void send_read_marker(string fully_read_event_id, string? read_receipt_event_id);
void send(RoomMessageEventContent msg, string? txn_id);
[Throws=ClientError]
void send_reply(string msg, string in_reply_to_event_id, string? txn_id);
[Throws=ClientError]
void edit(string new_msg, string original_event_id, string? txn_id);
[Throws=ClientError]
void redact(string event_id, string? reason, string? txn_id);
[Throws=ClientError]
void report_content(string event_id, i32? score, string? reason);
[Throws=ClientError]
void ignore_user(string user_id);
[Throws=ClientError]
void send_reaction(string event_id, string key);
[Throws=ClientError]
void leave();
[Throws=ClientError]
void reject_invitation();
[Throws=ClientError]
void accept_invitation();
[Throws=ClientError]
void set_topic(string topic);
[Throws=ClientError]
void upload_avatar(string mime_type, sequence<u8> data);
[Throws=ClientError]
void remove_avatar();
[Throws=ClientError]
void invite_user_by_id(string user_id);
};
callback interface TimelineListener {
void on_update(TimelineDiff update);
};
@@ -311,19 +150,8 @@ interface AuthenticationService {
constructor(string base_path, string? passphrase, string? custom_sliding_sync_proxy);
};
dictionary NotificationItem {
TimelineItem item;
string title;
string? subtitle;
boolean is_noisy;
string? avatar_url;
};
interface NotificationService {
constructor(string base_path, string user_id);
[Throws=ClientError]
NotificationItem? get_notification_item(string room_id, string event_id);
};
interface SessionVerificationEmoji {};
@@ -337,25 +165,6 @@ callback interface SessionVerificationControllerDelegate {
void did_finish();
};
interface SessionVerificationController {
void set_delegate(SessionVerificationControllerDelegate? delegate);
[Throws=ClientError]
void request_verification();
[Throws=ClientError]
void start_sas_verification();
[Throws=ClientError]
void approve_verification();
[Throws=ClientError]
void decline_verification();
[Throws=ClientError]
void cancel_verification();
};
interface Span {
constructor(string file, u32 line, u32 column, LogLevel level, string target, string name);
[Name=current]

View File

@@ -9,6 +9,7 @@ use url::Url;
use zeroize::Zeroize;
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
use crate::error::ClientError;
pub struct AuthenticationService {
base_path: String,
@@ -144,7 +145,7 @@ impl AuthenticationService {
}
}
let client = builder.build().or_else(|e| {
let client = builder.build_inner().or_else(|e| {
if !server_name_or_homeserver_url.starts_with("http://")
&& !server_name_or_homeserver_url.starts_with("https://")
{
@@ -153,7 +154,7 @@ impl AuthenticationService {
// When discovery fails, fallback to the homeserver URL if supplied.
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
builder = builder.homeserver_url(server_name_or_homeserver_url);
builder.build()
builder.build_inner()
})?;
let details = RUNTIME.block_on(self.details_from_client(&client))?;
@@ -186,7 +187,9 @@ impl AuthenticationService {
// Login and ask the server for the full user ID as this could be different from
// the username that was entered.
client.login(username, password, initial_device_name, device_id)?;
client.login(username, password, initial_device_name, device_id).map_err(|e| match e {
ClientError::Generic { msg } => AuthenticationError::Generic { message: msg },
})?;
let whoami = client.whoami()?;
// Create a new client to setup the store path now the user ID is known.
@@ -208,7 +211,7 @@ impl AuthenticationService {
.homeserver_url(homeserver_url)
.sliding_sync_proxy(sliding_sync_proxy)
.username(whoami.user_id.to_string())
.build()?;
.build_inner()?;
// Restore the client using the session from the login request.
client.restore_session_inner(session)?;
@@ -260,7 +263,7 @@ impl AuthenticationService {
.passphrase(self.passphrase.clone())
.homeserver_url(homeserver_url)
.username(whoami.user_id.to_string())
.build()?;
.build_inner()?;
// Restore the client using the session.
client.restore_session_inner(session)?;

View File

@@ -105,7 +105,7 @@ pub trait ClientDelegate: Sync + Send {
fn did_receive_auth_error(&self, is_soft_logout: bool);
}
#[derive(Clone)]
#[derive(Clone, uniffi::Object)]
pub struct Client {
pub(crate) client: MatrixClient,
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
@@ -161,7 +161,10 @@ impl Client {
client
}
}
#[uniffi::export]
impl Client {
/// Login using a username and password.
pub fn login(
&self,
@@ -169,7 +172,7 @@ impl Client {
password: String,
initial_device_name: Option<String>,
device_id: Option<String>,
) -> anyhow::Result<()> {
) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let mut builder = self.client.login_username(&username, &password);
if let Some(initial_device_name) = initial_device_name.as_ref() {
@@ -187,7 +190,7 @@ impl Client {
&self,
media_source: Arc<MediaSource>,
mime_type: String,
) -> anyhow::Result<Arc<MediaFileHandle>> {
) -> Result<Arc<MediaFileHandle>, ClientError> {
let client = self.client.clone();
let source = (*media_source).clone();
let mime_type: mime::Mime = mime_type.parse()?;
@@ -205,10 +208,7 @@ impl Client {
Ok(Arc::new(MediaFileHandle { inner: handle }))
})
}
}
#[uniffi::export]
impl Client {
/// Restores the client from a `Session`.
pub fn restore_session(&self, session: Session) -> Result<(), ClientError> {
let Session {
@@ -241,17 +241,13 @@ impl Client {
})
}
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
*self.delegate.write().unwrap() = delegate;
}
pub async fn async_homeserver(&self) -> String {
pub(crate) async fn async_homeserver(&self) -> String {
self.client.homeserver().await.to_string()
}
/// The OIDC Provider that is trusted by the homeserver. `None` when
/// not configured.
pub async fn authentication_issuer(&self) -> Option<String> {
pub(crate) async fn authentication_issuer(&self) -> Option<String> {
self.client.authentication_issuer().await
}
@@ -268,7 +264,7 @@ impl Client {
}
/// Whether or not the client's homeserver supports the password login flow.
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
pub(crate) async fn supports_password_login(&self) -> anyhow::Result<bool> {
let login_types = self.client.get_login_types().await?;
let supports_password = login_types
.flows
@@ -278,7 +274,7 @@ impl Client {
}
/// Gets information about the owner of a given access token.
pub fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
pub(crate) fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
RUNTIME
.block_on(async move { self.client.whoami().await.map_err(|e| anyhow!(e.to_string())) })
}
@@ -286,6 +282,10 @@ impl Client {
#[uniffi::export]
impl Client {
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
*self.delegate.write().unwrap() = delegate;
}
pub fn session(&self) -> Result<Session, ClientError> {
RUNTIME.block_on(async move {
let matrix_sdk::Session { access_token, refresh_token, user_id, device_id } =
@@ -722,10 +722,12 @@ fn gen_transaction_id() -> String {
/// A file handle that takes ownership of a media file on disk. When the handle
/// is dropped, the file will be removed from the disk.
#[derive(uniffi::Object)]
pub struct MediaFileHandle {
inner: SdkMediaFileHandle,
}
#[uniffi::export]
impl MediaFileHandle {
/// Get the media file's path.
pub fn path(&self) -> String {

View File

@@ -13,7 +13,7 @@ use sanitize_filename_reader_friendly::sanitize;
use zeroize::Zeroizing;
use super::{client::Client, RUNTIME};
use crate::helpers::unwrap_or_clone_arc;
use crate::{error::ClientError, helpers::unwrap_or_clone_arc};
#[derive(Clone)]
pub struct ClientBuilder {
@@ -28,6 +28,22 @@ pub struct ClientBuilder {
inner: MatrixClientBuilder,
}
impl ClientBuilder {
pub fn new() -> Self {
Self {
base_path: None,
username: None,
server_name: None,
homeserver_url: None,
server_versions: None,
passphrase: Zeroizing::new(None),
user_agent: None,
sliding_sync_proxy: None,
inner: MatrixClient::builder(),
}
}
}
#[uniffi::export]
impl ClientBuilder {
pub fn base_path(self: Arc<Self>, path: String) -> Arc<Self> {
@@ -77,24 +93,14 @@ impl ClientBuilder {
builder.sliding_sync_proxy = sliding_sync_proxy;
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientError> {
Ok(self.build_inner()?)
}
}
impl ClientBuilder {
pub fn new() -> Self {
Self {
base_path: None,
username: None,
server_name: None,
homeserver_url: None,
server_versions: None,
passphrase: Zeroizing::new(None),
user_agent: None,
sliding_sync_proxy: None,
inner: MatrixClient::builder(),
}
}
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
pub(crate) fn build_inner(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
let builder = unwrap_or_clone_arc(self);
let mut inner_builder = builder.inner;

View File

@@ -1,4 +1,4 @@
use matrix_sdk::{self, encryption::CryptoStoreError, HttpError, IdParseError};
use matrix_sdk::{self, encryption::CryptoStoreError, HttpError, IdParseError, StoreError};
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
@@ -18,6 +18,12 @@ impl From<matrix_sdk::Error> for ClientError {
}
}
impl From<StoreError> for ClientError {
fn from(e: StoreError) -> Self {
anyhow::Error::from(e).into()
}
}
impl From<CryptoStoreError> for ClientError {
fn from(e: CryptoStoreError) -> Self {
anyhow::Error::from(e).into()
@@ -41,3 +47,15 @@ impl From<serde_json::Error> for ClientError {
anyhow::Error::from(e).into()
}
}
impl From<url::ParseError> for ClientError {
fn from(e: url::ParseError) -> Self {
anyhow::Error::from(e).into()
}
}
impl From<mime::FromStrError> for ClientError {
fn from(e: mime::FromStrError) -> Self {
anyhow::Error::from(e).into()
}
}

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::TimelineItem;
use crate::{error::ClientError, TimelineItem};
#[allow(dead_code)]
pub struct NotificationService {
@@ -9,6 +9,7 @@ pub struct NotificationService {
}
/// Notification item struct.
#[derive(uniffi::Record)]
pub struct NotificationItem {
/// Actual timeline item for the event sent.
pub item: Arc<TimelineItem>,
@@ -32,7 +33,10 @@ impl NotificationService {
pub fn new(base_path: String, user_id: String) -> Self {
Self { base_path, user_id }
}
}
#[uniffi::export]
impl NotificationService {
/// Get notification item for a given `room_id `and `event_id`.
///
/// Returns `None` if this notification should not be displayed to the user.
@@ -40,7 +44,7 @@ impl NotificationService {
&self,
_room_id: String,
_event_id: String,
) -> anyhow::Result<Option<NotificationItem>> {
) -> Result<Option<NotificationItem>, ClientError> {
// TODO: Implement
Ok(None)
}

View File

@@ -98,7 +98,13 @@ pub fn create_otlp_tracer(
fn setup_tracing_helper(configuration: String, colors: bool) {
tracing_subscriber::registry()
.with(EnvFilter::new(configuration))
.with(fmt::layer().with_ansi(colors).with_writer(io::stderr))
.with(
fmt::layer()
.with_file(true)
.with_line_number(true)
.with_ansi(colors)
.with_writer(io::stderr),
)
.init();
}
@@ -116,7 +122,13 @@ fn setup_otlp_tracing_helper(
tracing_subscriber::registry()
.with(EnvFilter::new(configuration))
.with(fmt::layer().with_ansi(colors).with_writer(io::stderr))
.with(
fmt::layer()
.with_file(true)
.with_line_number(true)
.with_ansi(colors)
.with_writer(io::stderr),
)
.with(otlp_layer)
.init();

View File

@@ -3,7 +3,7 @@ use std::{
sync::{Arc, RwLock},
};
use anyhow::{bail, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use futures_util::StreamExt;
use matrix_sdk::{
room::{timeline::Timeline, Receipts, Room as SdkRoom},
@@ -19,12 +19,13 @@ use matrix_sdk::{
},
EventId, UserId,
},
RoomMemberships,
};
use mime::Mime;
use tracing::error;
use super::RUNTIME;
use crate::{RoomMember, TimelineDiff, TimelineItem, TimelineListener};
use crate::{error::ClientError, RoomMember, TimelineDiff, TimelineItem, TimelineListener};
#[derive(uniffi::Enum)]
pub enum Membership {
@@ -35,11 +36,22 @@ pub enum Membership {
pub(crate) type TimelineLock = Arc<RwLock<Option<Arc<Timeline>>>>;
#[derive(uniffi::Object)]
pub struct Room {
room: SdkRoom,
timeline: TimelineLock,
}
impl Room {
pub(crate) fn new(room: SdkRoom) -> Self {
Room { room, timeline: Default::default() }
}
pub(crate) fn with_timeline(room: SdkRoom, timeline: TimelineLock) -> Self {
Room { room, timeline }
}
}
#[uniffi::export]
impl Room {
pub fn id(&self) -> String {
@@ -139,22 +151,13 @@ impl Room {
timeline.fetch_members().await;
});
}
}
impl Room {
pub fn new(room: SdkRoom) -> Self {
Room { room, timeline: Default::default() }
}
pub fn with_timeline(room: SdkRoom, timeline: TimelineLock) -> Self {
Room { room, timeline }
}
pub fn display_name(&self) -> Result<String> {
pub fn display_name(&self) -> Result<String, ClientError> {
let r = self.room.clone();
RUNTIME.block_on(async move { Ok(r.display_name().await?.to_string()) })
}
pub fn is_encrypted(&self) -> Result<bool> {
pub fn is_encrypted(&self) -> Result<bool, ClientError> {
let room = self.room.clone();
RUNTIME.block_on(async move {
let is_encrypted = room.is_encrypted().await?;
@@ -162,11 +165,11 @@ impl Room {
})
}
pub fn members(&self) -> Result<Vec<Arc<RoomMember>>> {
pub fn members(&self) -> Result<Vec<Arc<RoomMember>>, ClientError> {
let room = self.room.clone();
RUNTIME.block_on(async move {
let members = room
.members()
.members(RoomMemberships::empty())
.await?
.iter()
.map(|m| Arc::new(RoomMember::new(m.clone())))
@@ -175,7 +178,7 @@ impl Room {
})
}
pub fn member_avatar_url(&self, user_id: String) -> Result<Option<String>> {
pub fn member_avatar_url(&self, user_id: String) -> Result<Option<String>, ClientError> {
let room = self.room.clone();
let user_id = user_id;
RUNTIME.block_on(async move {
@@ -186,7 +189,7 @@ impl Room {
})
}
pub fn member_display_name(&self, user_id: String) -> Result<Option<String>> {
pub fn member_display_name(&self, user_id: String) -> Result<Option<String>, ClientError> {
let room = self.room.clone();
let user_id = user_id;
RUNTIME.block_on(async move {
@@ -233,18 +236,25 @@ impl Room {
})
}
pub fn paginate_backwards(&self, opts: PaginationOptions) -> Result<()> {
/// Loads older messages into the timeline.
///
/// Raises an exception if there are no timeline listeners.
pub fn paginate_backwards(&self, opts: PaginationOptions) -> Result<(), ClientError> {
if let Some(timeline) = &*self.timeline.read().unwrap() {
RUNTIME.block_on(async move { Ok(timeline.paginate_backwards(opts.into()).await?) })
} else {
bail!("No timeline listeners registered, can't paginate");
Err(anyhow!("No timeline listeners registered, can't paginate").into())
}
}
pub fn send_read_receipt(&self, event_id: String) -> Result<()> {
pub fn send_read_receipt(&self, event_id: String) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't send read receipts to room that isn't in joined state"),
_ => {
return Err(
anyhow!("Can't send read receipts to room that isn't in joined state").into()
)
}
};
let event_id = EventId::parse(event_id)?;
@@ -260,10 +270,14 @@ impl Room {
&self,
fully_read_event_id: String,
read_receipt_event_id: Option<String>,
) -> Result<()> {
) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't send read markers to room that isn't in joined state"),
_ => {
return Err(
anyhow!("Can't send read markers to room that isn't in joined state").into()
)
}
};
let fully_read =
@@ -300,15 +314,15 @@ impl Room {
msg: String,
in_reply_to_event_id: String,
txn_id: Option<String>,
) -> Result<()> {
) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't send to a room that isn't in joined state"),
_ => return Err(anyhow!("Can't send to a room that isn't in joined state").into()),
};
let timeline = match &*self.timeline.read().unwrap() {
Some(t) => Arc::clone(t),
None => bail!("Timeline not set up, can't send message"),
None => return Err(anyhow!("Timeline not set up, can't send message").into()),
};
let event_id: &EventId =
@@ -342,15 +356,15 @@ impl Room {
new_msg: String,
original_event_id: String,
txn_id: Option<String>,
) -> Result<()> {
) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't send to a room that isn't in joined state"),
_ => return Err(anyhow!("Can't send to a room that isn't in joined state").into()),
};
let timeline = match &*self.timeline.read().unwrap() {
Some(t) => Arc::clone(t),
None => bail!("Timeline not set up, can't send message"),
None => return Err(anyhow!("Timeline not set up, can't send message").into()),
};
let event_id: &EventId =
@@ -365,7 +379,7 @@ impl Room {
.context("Couldn't deserialise event")?;
if self.own_user_id() != event_content.sender() {
bail!("Can't edit an event not sent by own user")
bail!("Can't edit an event not sent by own user");
}
let replacement = Replacement::new(
@@ -399,10 +413,10 @@ impl Room {
event_id: String,
reason: Option<String>,
txn_id: Option<String>,
) -> Result<()> {
) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't redact in a room that isn't in joined state"),
_ => return Err(anyhow!("Can't redact in a room that isn't in joined state").into()),
};
RUNTIME.block_on(async move {
@@ -412,10 +426,14 @@ impl Room {
})
}
pub fn send_reaction(&self, event_id: String, key: String) -> Result<()> {
pub fn send_reaction(&self, event_id: String, key: String) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't send reaction in a room that isn't in joined state"),
_ => {
return Err(
anyhow!("Can't send reaction in a room that isn't in joined state").into()
)
}
};
RUNTIME.block_on(async move {
@@ -440,7 +458,7 @@ impl Room {
event_id: String,
score: Option<i32>,
reason: Option<String>,
) -> Result<()> {
) -> Result<(), ClientError> {
let int_score = score.map(|value| value.into());
RUNTIME.block_on(async move {
let event_id = EventId::parse(event_id)?;
@@ -465,7 +483,7 @@ impl Room {
/// # Arguments
///
/// * `event_id` - The ID of the user to ignore.
pub fn ignore_user(&self, user_id: String) -> Result<()> {
pub fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let user_id = UserId::parse(user_id)?;
self.client().account().ignore_user(&user_id).await?;
@@ -476,10 +494,10 @@ impl Room {
/// Leaves the joined room.
///
/// Will throw an error if used on an room that isn't in a joined state
pub fn leave(&self) -> Result<()> {
pub fn leave(&self) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't leave a room that isn't in joined state"),
_ => return Err(anyhow!("Can't leave a room that isn't in joined state").into()),
};
RUNTIME.block_on(async move {
@@ -491,10 +509,15 @@ impl Room {
/// Rejects the invitation for the invited room.
///
/// Will throw an error if used on an room that isn't in an invited state
pub fn reject_invitation(&self) -> Result<()> {
pub fn reject_invitation(&self) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Invited(i) => i.clone(),
_ => bail!("Can't reject an invite for a room that isn't in invited state"),
_ => {
return Err(anyhow!(
"Can't reject an invite for a room that isn't in invited state"
)
.into())
}
};
RUNTIME.block_on(async move {
@@ -506,10 +529,15 @@ impl Room {
/// Accepts the invitation for the invited room.
///
/// Will throw an error if used on an room that isn't in an invited state
pub fn accept_invitation(&self) -> Result<()> {
pub fn accept_invitation(&self) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Invited(i) => i.clone(),
_ => bail!("Can't accept an invite for a room that isn't in invited state"),
_ => {
return Err(anyhow!(
"Can't accept an invite for a room that isn't in invited state"
)
.into())
}
};
RUNTIME.block_on(async move {
@@ -519,10 +547,12 @@ impl Room {
}
/// Sets a new topic in the room.
pub fn set_topic(&self, topic: String) -> Result<()> {
pub fn set_topic(&self, topic: String) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't set a topic in a room that isn't in joined state"),
_ => {
return Err(anyhow!("Can't set a topic in a room that isn't in joined state").into())
}
};
RUNTIME.block_on(async move {
@@ -543,10 +573,14 @@ impl Room {
/// image/jpeg
/// * `data` - The raw data that will be uploaded to the homeserver's
/// content repository
pub fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<()> {
pub fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't set a avatar in a room that isn't in joined state"),
_ => {
return Err(
anyhow!("Can't set a avatar in a room that isn't in joined state").into()
)
}
};
RUNTIME.block_on(async move {
@@ -558,10 +592,14 @@ impl Room {
}
/// Removes the current room avatar
pub fn remove_avatar(&self) -> Result<()> {
pub fn remove_avatar(&self) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(j) => j.clone(),
_ => bail!("Can't remove a avatar in a room that isn't in joined state"),
_ => {
return Err(
anyhow!("Can't remove a avatar in a room that isn't in joined state").into()
)
}
};
RUNTIME.block_on(async move {
@@ -570,10 +608,10 @@ impl Room {
})
}
pub fn invite_user_by_id(&self, user_id: String) -> Result<()> {
pub fn invite_user_by_id(&self, user_id: String) -> Result<(), ClientError> {
let room = match &self.room {
SdkRoom::Joined(joined_room) => joined_room.clone(),
_ => bail!("Can't invite user to room that isn't in joined state"),
_ => return Err(anyhow!("Can't invite user to room that isn't in joined state").into()),
};
RUNTIME.block_on(async move {
@@ -583,6 +621,22 @@ impl Room {
Ok(())
})
}
pub fn fetch_event_details(&self, event_id: String) -> Result<(), ClientError> {
let timeline = self
.timeline
.read()
.unwrap()
.as_ref()
.context("Timeline not set up, can't fetch event details")?
.clone();
RUNTIME.block_on(async move {
let event_id = <&EventId>::try_from(event_id.as_str())?;
timeline.fetch_event_details(event_id).await.context("Fetching event details")?;
Ok(())
})
}
}
impl std::ops::Deref for Room {

View File

@@ -3,7 +3,7 @@ use matrix_sdk::room::RoomMember as SdkRoomMember;
use super::RUNTIME;
use crate::ClientError;
#[derive(Clone)]
#[derive(Clone, uniffi::Enum)]
pub enum MembershipState {
/// The user is banned.
Ban,

View File

@@ -7,13 +7,11 @@ use matrix_sdk::{
verification::{SasState, SasVerification, VerificationRequest},
Encryption,
},
ruma::{
events::{key::verification::VerificationMethod, AnyToDeviceEvent},
serde::Raw,
},
ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent},
};
use super::RUNTIME;
use crate::error::ClientError;
pub struct SessionVerificationEmoji {
symbol: String,
@@ -42,7 +40,7 @@ pub trait SessionVerificationControllerDelegate: Sync + Send {
pub type Delegate = Arc<RwLock<Option<Box<dyn SessionVerificationControllerDelegate>>>>;
#[derive(Clone)]
#[derive(Clone, uniffi::Object)]
pub struct SessionVerificationController {
encryption: Encryption,
user_identity: UserIdentity,
@@ -56,35 +54,26 @@ impl SessionVerificationController {
pub fn is_verified(&self) -> bool {
self.user_identity.is_verified()
}
}
impl SessionVerificationController {
pub fn new(encryption: Encryption, user_identity: UserIdentity) -> Self {
SessionVerificationController {
encryption,
user_identity,
delegate: Arc::new(RwLock::new(None)),
verification_request: Arc::new(RwLock::new(None)),
sas_verification: Arc::new(RwLock::new(None)),
}
}
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
*self.delegate.write().unwrap() = delegate;
}
pub fn request_verification(&self) -> anyhow::Result<()> {
pub fn request_verification(&self) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let methods = vec![VerificationMethod::SasV1];
let verification_request =
self.user_identity.request_verification_with_methods(methods).await?;
let verification_request = self
.user_identity
.request_verification_with_methods(methods)
.await
.map_err(anyhow::Error::from)?;
*self.verification_request.write().unwrap() = Some(verification_request);
Ok(())
})
}
pub fn start_sas_verification(&self) -> anyhow::Result<()> {
pub fn start_sas_verification(&self) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let verification_request = self.verification_request.read().unwrap().clone();
@@ -112,7 +101,7 @@ impl SessionVerificationController {
})
}
pub fn approve_verification(&self) -> anyhow::Result<()> {
pub fn approve_verification(&self) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
@@ -123,7 +112,7 @@ impl SessionVerificationController {
})
}
pub fn decline_verification(&self) -> anyhow::Result<()> {
pub fn decline_verification(&self) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
@@ -134,7 +123,7 @@ impl SessionVerificationController {
})
}
pub fn cancel_verification(&self) -> anyhow::Result<()> {
pub fn cancel_verification(&self) -> Result<(), ClientError> {
RUNTIME.block_on(async move {
let verification_request = self.verification_request.read().unwrap().clone();
if let Some(verification) = verification_request {
@@ -144,8 +133,20 @@ impl SessionVerificationController {
Ok(())
})
}
}
pub async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
impl SessionVerificationController {
pub(crate) fn new(encryption: Encryption, user_identity: UserIdentity) -> Self {
SessionVerificationController {
encryption,
user_identity,
delegate: Arc::new(RwLock::new(None)),
verification_request: Arc::new(RwLock::new(None)),
sas_verification: Arc::new(RwLock::new(None)),
}
}
pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
match event {
// TODO: Use the changes stream for this as well once we expose
// VerificationRequest::changes() in the main crate.
@@ -190,12 +191,6 @@ impl SessionVerificationController {
}
}
pub async fn process_to_device_messages(&self, to_device_events: Vec<Raw<AnyToDeviceEvent>>) {
for event in to_device_events.into_iter().filter_map(|e| e.deserialize().ok()) {
self.process_to_device_message(event).await;
}
}
fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
match &*self.verification_request.read().unwrap() {
Some(verification) => verification.flow_id() == transaction_id,

View File

@@ -20,12 +20,13 @@ use tracing::{debug, error, warn};
use url::Url;
use crate::{
helpers::unwrap_or_clone_arc, room::TimelineLock, Client, EventTimelineItem, Room,
TimelineDiff, TimelineItem, TimelineListener, RUNTIME,
error::ClientError, helpers::unwrap_or_clone_arc, room::TimelineLock, Client,
EventTimelineItem, Room, TimelineDiff, TimelineItem, TimelineListener, RUNTIME,
};
type TaskHandleFinalizer = Box<dyn FnOnce() + Send + Sync>;
#[derive(uniffi::Object)]
pub struct TaskHandle {
handle: JoinHandle<()>,
finalizer: RwLock<Option<TaskHandleFinalizer>>,
@@ -154,6 +155,7 @@ impl From<matrix_sdk::sliding_sync::Error> for SlidingSyncError {
}
}
#[derive(uniffi::Object)]
pub struct SlidingSyncRoom {
inner: matrix_sdk::SlidingSyncRoom,
timeline: TimelineLock,
@@ -206,13 +208,11 @@ impl SlidingSyncRoom {
let item = RUNTIME.block_on(self.inner.latest_event())?;
Some(Arc::new(EventTimelineItem(item)))
}
}
impl SlidingSyncRoom {
pub fn add_timeline_listener(
&self,
listener: Box<dyn TimelineListener>,
) -> anyhow::Result<SlidingSyncSubscribeResult> {
) -> Result<SlidingSyncSubscribeResult, ClientError> {
let (items, stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
@@ -222,7 +222,7 @@ impl SlidingSyncRoom {
&self,
listener: Box<dyn TimelineListener>,
settings: Option<RoomSubscription>,
) -> anyhow::Result<SlidingSyncSubscribeResult> {
) -> Result<SlidingSyncSubscribeResult, ClientError> {
let (items, mut stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
let room_id = self.inner.room_id().clone();
@@ -233,7 +233,9 @@ impl SlidingSyncRoom {
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
}
}
impl SlidingSyncRoom {
fn add_timeline_listener_inner(
&self,
listener: Box<dyn TimelineListener>,
@@ -312,6 +314,7 @@ impl SlidingSyncRoom {
}
}
#[derive(uniffi::Record)]
pub struct SlidingSyncSubscribeResult {
pub items: Vec<Arc<TimelineItem>>,
pub task_handle: Arc<TaskHandle>,
@@ -323,11 +326,13 @@ pub struct UpdateSummary {
pub rooms: Vec<String>,
}
#[derive(uniffi::Record)]
pub struct RequiredState {
pub key: String,
pub value: String,
}
#[derive(uniffi::Record)]
pub struct RoomSubscription {
pub required_state: Option<Vec<RequiredState>>,
pub timeline_limit: Option<u32>,
@@ -470,27 +475,21 @@ impl SlidingSyncListBuilder {
pub fn new() -> Self {
Self { inner: matrix_sdk::SlidingSyncList::builder() }
}
}
#[uniffi::export]
impl SlidingSyncListBuilder {
pub fn sync_mode(self: Arc<Self>, mode: SlidingSyncMode) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.sync_mode(mode);
Arc::new(builder)
}
pub fn ranges(self: Arc<Self>, ranges: Vec<(u32, u32)>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.ranges(ranges);
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncList>> {
pub fn build(self: Arc<Self>) -> Result<Arc<SlidingSyncList>, ClientError> {
let builder = unwrap_or_clone_arc(self);
Ok(Arc::new(builder.inner.build()?.into()))
}
}
#[uniffi::export]
impl SlidingSyncListBuilder {
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.sort(sort);
@@ -566,7 +565,7 @@ impl SlidingSyncListBuilder {
}
}
#[derive(Clone)]
#[derive(Clone, uniffi::Object)]
pub struct SlidingSyncList {
inner: matrix_sdk::SlidingSyncList,
}
@@ -577,6 +576,7 @@ impl From<matrix_sdk::SlidingSyncList> for SlidingSyncList {
}
}
#[uniffi::export]
impl SlidingSyncList {
pub fn observe_state(
&self,
@@ -622,10 +622,7 @@ impl SlidingSyncList {
}
})))
}
}
#[uniffi::export]
impl SlidingSyncList {
/// Get the current list of rooms
pub fn current_room_list(&self) -> Vec<RoomListEntry> {
self.inner.room_list()
@@ -677,6 +674,7 @@ pub trait SlidingSyncObserver: Sync + Send {
fn did_receive_sync_update(&self, summary: UpdateSummary);
}
#[derive(uniffi::Object)]
pub struct SlidingSync {
inner: matrix_sdk::SlidingSync,
client: Client,
@@ -687,7 +685,10 @@ impl SlidingSync {
fn new(inner: matrix_sdk::SlidingSync, client: Client) -> Self {
Self { inner, client, observer: Default::default() }
}
}
#[uniffi::export]
impl SlidingSync {
pub fn set_observer(&self, observer: Option<Box<dyn SlidingSyncObserver>>) {
*self.observer.write().unwrap() = observer;
}
@@ -696,17 +697,17 @@ impl SlidingSync {
&self,
room_id: String,
settings: Option<RoomSubscription>,
) -> anyhow::Result<()> {
) -> Result<(), ClientError> {
self.inner.subscribe(room_id.try_into()?, settings.map(Into::into));
Ok(())
}
pub fn unsubscribe(&self, room_id: String) -> anyhow::Result<()> {
pub fn unsubscribe(&self, room_id: String) -> Result<(), ClientError> {
self.inner.unsubscribe(room_id.try_into()?);
Ok(())
}
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
pub fn get_room(&self, room_id: String) -> Result<Option<Arc<SlidingSyncRoom>>, ClientError> {
let runner = self.inner.clone();
Ok(self.inner.get_room(<&RoomId>::try_from(room_id.as_str())?).map(|inner| {
@@ -722,7 +723,7 @@ impl SlidingSync {
pub fn get_rooms(
&self,
room_ids: Vec<String>,
) -> anyhow::Result<Vec<Option<Arc<SlidingSyncRoom>>>> {
) -> Result<Vec<Option<Arc<SlidingSyncRoom>>>, ClientError> {
let actual_ids = room_ids
.into_iter()
.map(OwnedRoomId::try_from)
@@ -743,10 +744,7 @@ impl SlidingSync {
})
.collect())
}
}
#[uniffi::export]
impl SlidingSync {
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn get_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
self.inner.list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
@@ -800,29 +798,20 @@ impl SlidingSync {
}
}
#[derive(Clone)]
#[derive(Clone, uniffi::Object)]
pub struct SlidingSyncBuilder {
inner: MatrixSlidingSyncBuilder,
client: Client,
}
#[uniffi::export]
impl SlidingSyncBuilder {
pub fn homeserver(self: Arc<Self>, url: String) -> anyhow::Result<Arc<Self>> {
pub fn homeserver(self: Arc<Self>, url: String) -> Result<Arc<Self>, ClientError> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.homeserver(url.parse()?);
Ok(Arc::new(builder))
}
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSync>> {
let builder = unwrap_or_clone_arc(self);
RUNTIME.block_on(async move {
Ok(Arc::new(SlidingSync::new(builder.inner.build().await?, builder.client)))
})
}
}
#[uniffi::export]
impl SlidingSyncBuilder {
pub fn storage_key(self: Arc<Self>, name: Option<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.storage_key(name);
@@ -877,6 +866,21 @@ impl SlidingSyncBuilder {
builder.inner = builder.inner.with_all_extensions();
Arc::new(builder)
}
pub fn bump_event_types(self: Arc<Self>, bump_event_types: Vec<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.bump_event_types(
bump_event_types.into_iter().map(Into::into).collect::<Vec<_>>().as_slice(),
);
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> Result<Arc<SlidingSync>, ClientError> {
let builder = unwrap_or_clone_arc(self);
RUNTIME.block_on(async move {
Ok(Arc::new(SlidingSync::new(builder.inner.build().await?, builder.client)))
})
}
}
#[uniffi::export]

View File

@@ -550,7 +550,7 @@ mod tests {
use matrix_sdk::{
config::RequestConfig,
ruma::{api::appservice::Registration, events::room::member::OriginalSyncRoomMemberEvent},
Client,
Client, RoomMemberships,
};
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, TimelineTestEvent};
use ruma::{
@@ -888,7 +888,7 @@ mod tests {
.await?
.get_room(room_id)
.expect("Expected room to be available")
.members_no_sync()
.members_no_sync(RoomMemberships::empty())
.await?;
assert_eq!(members[0].display_name().unwrap(), "changed");

View File

@@ -30,6 +30,7 @@ testing = ["dep:http", "dep:matrix-sdk-test", "dep:assert_matches"]
assert_matches = { workspace = true, optional = true }
async-stream = { workspace = true }
async-trait = { workspace = true }
bitflags = "2.1.0"
dashmap = { workspace = true }
eyeball = { workspace = true }
futures-core = "0.3.21"

View File

@@ -6,6 +6,12 @@
- Add `RoomInfo::state` accessor
- Remove `members` and `stripped_members` fields in `StateChanges`. Room member events are now with
other state events in `state` and `stripped_state`.
- `StateStore::get_user_ids` takes a `RoomMemberships` to be able to filter the results by any
membership state.
- `StateStore::get_joined_user_ids` and `StateStore::get_invited_user_ids` are deprecated.
- `Room::members` takes a `RoomMemberships` to be able to filter the results by any membership
state.
- `Room::active_members` and `Room::joined_members` are deprecated.
## 0.5.1

View File

@@ -56,8 +56,6 @@ use ruma::{
use tokio::sync::RwLock;
use tracing::{debug, info, trace, warn};
#[cfg(feature = "e2e-encryption")]
use crate::error::Error;
use crate::{
deserialized_responses::{AmbiguityChanges, MembersResponse, SyncTimelineEvent},
error::Result,
@@ -69,6 +67,8 @@ use crate::{
sync::{JoinedRoom, LeftRoom, Rooms, SyncResponse, Timeline},
Session, SessionMeta, SessionTokens,
};
#[cfg(feature = "e2e-encryption")]
use crate::{error::Error, RoomMemberships};
/// A no IO Client implementation.
///
@@ -757,12 +757,9 @@ impl BaseClient {
// The room turned on encryption in this sync, we need
// to also get all the existing users and mark them for
// tracking.
let joined = self.store.get_joined_user_ids(&room_id).await?;
let invited = self.store.get_invited_user_ids(&room_id).await?;
let user_ids: Vec<&UserId> =
joined.iter().chain(&invited).map(Deref::deref).collect();
o.update_tracked_users(user_ids).await?
let user_ids =
self.store.get_user_ids(&room_id, RoomMemberships::ACTIVE).await?;
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
}
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?;
@@ -1058,21 +1055,20 @@ impl BaseClient {
.map(|r| (r.history_visibility(), r.encryption_settings()))
.unwrap_or((HistoryVisibility::Joined, None));
let joined = self.store.get_joined_user_ids(room_id).await?;
let invited = self.store.get_invited_user_ids(room_id).await?;
// Don't share the group session with members that are invited
// if the history visibility is set to `Joined`
let members = if history_visibility == HistoryVisibility::Joined {
joined.iter().chain(&[])
let filter = if history_visibility == HistoryVisibility::Joined {
RoomMemberships::JOIN
} else {
joined.iter().chain(&invited)
RoomMemberships::ACTIVE
};
let members = self.store.get_user_ids(room_id, filter).await?;
let settings = settings.ok_or(Error::EncryptionNotEnabled)?;
let settings = EncryptionSettings::new(settings, history_visibility, false);
Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?)
Ok(o.share_room_key(room_id, members.iter().map(Deref::deref), settings).await?)
}
None => panic!("Olm machine wasn't started"),
}

View File

@@ -42,7 +42,7 @@ pub use http;
#[cfg(feature = "e2e-encryption")]
pub use matrix_sdk_crypto as crypto;
pub use once_cell;
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomState};
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomMemberships, RoomState};
pub use store::{StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError};
pub use utils::{
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,

View File

@@ -3,6 +3,7 @@ mod normal;
use std::{collections::HashSet, fmt};
use bitflags::bitflags;
pub use members::RoomMember;
pub use normal::{Room, RoomInfo, RoomState};
use ruma::{
@@ -13,9 +14,9 @@ use ruma::{
create::RoomCreateEventContent, encryption::RoomEncryptionEventContent,
guest_access::RoomGuestAccessEventContent,
history_visibility::RoomHistoryVisibilityEventContent,
join_rules::RoomJoinRulesEventContent, name::RoomNameEventContent,
redaction::OriginalSyncRoomRedactionEvent, tombstone::RoomTombstoneEventContent,
topic::RoomTopicEventContent,
join_rules::RoomJoinRulesEventContent, member::MembershipState,
name::RoomNameEventContent, redaction::OriginalSyncRoomRedactionEvent,
tombstone::RoomTombstoneEventContent, topic::RoomTopicEventContent,
},
AnyStrippedStateEvent, AnySyncStateEvent, RedactContent, RedactedStateEventContent,
StaticStateEventContent, SyncStateEvent,
@@ -307,6 +308,49 @@ fn calculate_room_name(
}
}
bitflags! {
/// Room membership filter as a bitset.
///
/// Note that [`RoomMemberships::empty()`] doesn't filter the results and
/// [`RoomMemberships::all()`] filters out unknown memberships.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoomMemberships: u16 {
/// The member joined the room.
const JOIN = 0b00000001;
/// The member was invited to the room.
const INVITE = 0b00000010;
/// The member requested to join the room.
const KNOCK = 0b00000100;
/// The member left the room.
const LEAVE = 0b00001000;
/// The member was banned.
const BAN = 0b00010000;
/// The member is active in the room (i.e. joined or invited).
const ACTIVE = Self::JOIN.bits() | Self::INVITE.bits();
}
}
impl RoomMemberships {
/// Whether the given membership matches this `RoomMemberships`.
pub fn matches(&self, membership: &MembershipState) -> bool {
if self.is_empty() {
return true;
}
let membership = match membership {
MembershipState::Ban => Self::BAN,
MembershipState::Invite => Self::INVITE,
MembershipState::Join => Self::JOIN,
MembershipState::Knock => Self::KNOCK,
MembershipState::Leave => Self::LEAVE,
_ => return false,
};
self.contains(membership)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -44,7 +44,7 @@ use crate::{
deserialized_responses::MemberEvent,
store::{DynStateStore, Result as StoreResult, StateStoreExt},
sync::UnreadNotificationsCount,
MinimalStateEvent,
MinimalStateEvent, RoomMemberships,
};
/// The underlying room data structure collecting state for joined, left and
@@ -318,12 +318,13 @@ impl Room {
/// Get the list of users ids that are considered to be joined members of
/// this room.
pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
self.store.get_joined_user_ids(self.room_id()).await
self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
}
/// Get the all `RoomMember`s of this room that are known to the store.
pub async fn members(&self) -> StoreResult<Vec<RoomMember>> {
let user_ids = self.store.get_user_ids(self.room_id()).await?;
/// Get the `RoomMember`s of this room that are known to the store, with the
/// given memberships.
pub async fn members(&self, memberships: RoomMemberships) -> StoreResult<Vec<RoomMember>> {
let user_ids = self.store.get_user_ids(self.room_id(), memberships).await?;
let mut members = Vec::new();
for u in user_ids {
@@ -339,38 +340,16 @@ impl Room {
/// Get the list of `RoomMember`s that are considered to be joined members
/// of this room.
#[deprecated = "Use members with RoomMemberships::JOIN instead"]
pub async fn joined_members(&self) -> StoreResult<Vec<RoomMember>> {
let joined = self.store.get_joined_user_ids(self.room_id()).await?;
let mut members = Vec::new();
for u in joined {
let m = self.get_member(&u).await?;
if let Some(member) = m {
members.push(member);
}
}
Ok(members)
self.members(RoomMemberships::JOIN).await
}
/// Get the list of `RoomMember`s that are considered to be joined or
/// invited members of this room.
#[deprecated = "Use members with RoomMemberships::ACTIVE instead"]
pub async fn active_members(&self) -> StoreResult<Vec<RoomMember>> {
let joined = self.store.get_joined_user_ids(self.room_id()).await?;
let invited = self.store.get_invited_user_ids(self.room_id()).await?;
let mut members = Vec::new();
for u in joined.iter().chain(&invited) {
let m = self.get_member(u).await?;
if let Some(member) = m {
members.push(member);
}
}
Ok(members)
self.members(RoomMemberships::ACTIVE).await
}
async fn calculate_name(&self) -> StoreResult<DisplayName> {
@@ -391,7 +370,12 @@ impl Room {
let is_own_user_id = |u: &str| u == self.own_user_id().as_str();
let members: Vec<RoomMember> = if summary.heroes.is_empty() {
self.active_members().await?.into_iter().filter(|u| !is_own_member(u)).take(5).collect()
self.members(RoomMemberships::ACTIVE)
.await?
.into_iter()
.filter(|u| !is_own_member(u))
.take(5)
.collect()
} else {
let members: Vec<_> =
stream::iter(summary.heroes.iter().filter(|u| !is_own_user_id(u)))

View File

@@ -2,8 +2,6 @@ use std::collections::BTreeMap;
#[cfg(feature = "e2e-encryption")]
use std::ops::Deref;
#[cfg(feature = "e2e-encryption")]
use ruma::UserId;
use ruma::{
api::client::sync::sync_events::{
v3::{self, Ephemeral},
@@ -14,6 +12,8 @@ use ruma::{
use tracing::{debug, info, instrument};
use super::BaseClient;
#[cfg(feature = "e2e-encryption")]
use crate::RoomMemberships;
use crate::{
deserialized_responses::AmbiguityChanges,
error::Result,
@@ -196,12 +196,9 @@ impl BaseClient {
// The room turned on encryption in this sync, we need
// to also get all the existing users and mark them for
// tracking.
let joined = store.get_joined_user_ids(room_id).await?;
let invited = store.get_invited_user_ids(room_id).await?;
let user_ids: Vec<&UserId> =
joined.iter().chain(&invited).map(Deref::deref).collect();
o.update_tracked_users(user_ids).await?
let user_ids =
store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
}
if !user_ids.is_empty() {

View File

@@ -35,7 +35,7 @@ use crate::{
deserialized_responses::MemberEvent,
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
store::{Result, StateStoreExt},
RoomInfo, RoomState, StateChanges, StateStoreDataKey, StateStoreDataValue,
RoomInfo, RoomMemberships, RoomState, StateChanges, StateStoreDataKey, StateStoreDataValue,
};
/// `StateStore` integration tests.
@@ -330,17 +330,17 @@ impl StateStoreIntegrationTests for DynStateStore {
assert!(self.get_profile(room_id, user_id).await?.is_some());
assert!(self.get_member_event(room_id, user_id).await?.is_some());
assert_eq!(
self.get_user_ids(room_id).await?.len(),
self.get_user_ids(room_id, RoomMemberships::empty()).await?.len(),
2,
"Expected to find 2 members for room"
);
assert_eq!(
self.get_invited_user_ids(room_id).await?.len(),
self.get_user_ids(room_id, RoomMemberships::INVITE).await?.len(),
1,
"Expected to find 1 invited user ids"
);
assert_eq!(
self.get_joined_user_ids(room_id).await?.len(),
self.get_user_ids(room_id, RoomMemberships::JOIN).await?.len(),
1,
"Expected to find 1 joined user ids"
);
@@ -394,7 +394,7 @@ impl StateStoreIntegrationTests for DynStateStore {
self.save_changes(&changes).await.unwrap();
assert!(self.get_member_event(room_id, user_id).await.unwrap().is_some());
let members = self.get_user_ids(room_id).await.unwrap();
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
assert!(!members.is_empty(), "We expected to find members for the room")
}
@@ -466,7 +466,7 @@ impl StateStoreIntegrationTests for DynStateStore {
self.save_changes(&changes).await.unwrap();
assert!(self.get_member_event(room_id, user_id).await.unwrap().is_some());
let members = self.get_user_ids(room_id).await.unwrap();
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
assert!(!members.is_empty(), "We expected to find members for the room")
}
@@ -774,7 +774,7 @@ impl StateStoreIntegrationTests for DynStateStore {
assert_eq!(self.get_room_infos().await.unwrap().len(), 1);
assert_eq!(self.get_stripped_room_infos().await.unwrap().len(), 0);
let members = self.get_user_ids(room_id).await.unwrap();
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
assert_eq!(members, vec![user_id.to_owned()]);
let mut changes = StateChanges::default();
@@ -788,7 +788,7 @@ impl StateStoreIntegrationTests for DynStateStore {
assert_eq!(self.get_room_infos().await.unwrap().len(), 0);
assert_eq!(self.get_stripped_room_infos().await.unwrap().len(), 1);
let members = self.get_user_ids(room_id).await.unwrap();
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
assert_eq!(members, vec![user_id.to_owned()]);
Ok(())
@@ -813,12 +813,18 @@ impl StateStoreIntegrationTests for DynStateStore {
);
assert!(self.get_profile(room_id, user_id).await?.is_none());
assert!(self.get_member_event(room_id, user_id).await?.is_none());
assert!(self.get_user_ids(room_id).await?.is_empty(), "still user ids found");
assert!(
self.get_invited_user_ids(room_id).await?.is_empty(),
self.get_user_ids(room_id, RoomMemberships::empty()).await?.is_empty(),
"still user ids found"
);
assert!(
self.get_user_ids(room_id, RoomMemberships::INVITE).await?.is_empty(),
"still invited user ids found"
);
assert!(self.get_joined_user_ids(room_id).await?.is_empty(), "still joined users found");
assert!(
self.get_user_ids(room_id, RoomMemberships::JOIN).await?.is_empty(),
"still joined users found"
);
assert!(
self.get_users_with_display_name(room_id, "example").await?.is_empty(),
"still display names found"

View File

@@ -18,7 +18,7 @@ use std::{
};
use async_trait::async_trait;
use dashmap::{DashMap, DashSet};
use dashmap::DashMap;
use matrix_sdk_common::instant::Instant;
use ruma::{
canonical_json::redact,
@@ -38,7 +38,7 @@ use tracing::{debug, warn};
use super::{Result, RoomInfo, StateChanges, StateStore, StoreError};
use crate::{
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent,
StateStoreDataKey, StateStoreDataValue,
RoomMemberships, StateStoreDataKey, StateStoreDataValue,
};
/// In-Memory, non-persistent implementation of the `StateStore`
@@ -51,11 +51,9 @@ pub struct MemoryStore {
sync_token: Arc<RwLock<Option<String>>>,
filters: Arc<DashMap<String, String>>,
account_data: Arc<DashMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>>,
members: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
profiles: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MinimalRoomMemberEvent>>>,
display_names: Arc<DashMap<OwnedRoomId, DashMap<String, BTreeSet<OwnedUserId>>>>,
joined_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
invited_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
members: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MembershipState>>>,
room_info: Arc<DashMap<OwnedRoomId, RoomInfo>>,
room_state:
Arc<DashMap<OwnedRoomId, DashMap<StateEventType, DashMap<String, Raw<AnySyncStateEvent>>>>>,
@@ -65,9 +63,7 @@ pub struct MemoryStore {
stripped_room_state: Arc<
DashMap<OwnedRoomId, DashMap<StateEventType, DashMap<String, Raw<AnyStrippedStateEvent>>>>,
>,
stripped_members: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
stripped_joined_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
stripped_invited_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
stripped_members: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MembershipState>>>,
presence: Arc<DashMap<OwnedUserId, Raw<PresenceEvent>>>,
room_user_receipts: Arc<
DashMap<
@@ -99,19 +95,15 @@ impl MemoryStore {
sync_token: Default::default(),
filters: Default::default(),
account_data: Default::default(),
members: Default::default(),
profiles: Default::default(),
display_names: Default::default(),
joined_user_ids: Default::default(),
invited_user_ids: Default::default(),
members: Default::default(),
room_info: Default::default(),
room_state: Default::default(),
room_account_data: Default::default(),
stripped_room_infos: Default::default(),
stripped_room_state: Default::default(),
stripped_members: Default::default(),
stripped_joined_user_ids: Default::default(),
stripped_invited_user_ids: Default::default(),
presence: Default::default(),
room_user_receipts: Default::default(),
room_event_receipts: Default::default(),
@@ -240,47 +232,12 @@ impl MemoryStore {
}
};
self.stripped_joined_user_ids.remove(room);
self.stripped_invited_user_ids.remove(room);
match event.membership() {
MembershipState::Join => {
self.joined_user_ids
.entry(room.clone())
.or_default()
.insert(event.state_key().to_owned());
self.invited_user_ids
.entry(room.clone())
.or_default()
.remove(event.state_key());
}
MembershipState::Invite => {
self.invited_user_ids
.entry(room.clone())
.or_default()
.insert(event.state_key().to_owned());
self.joined_user_ids
.entry(room.clone())
.or_default()
.remove(event.state_key());
}
_ => {
self.joined_user_ids
.entry(room.clone())
.or_default()
.remove(event.state_key());
self.invited_user_ids
.entry(room.clone())
.or_default()
.remove(event.state_key());
}
}
self.stripped_members.remove(room);
self.members
.entry(room.clone())
.or_default()
.insert(event.state_key().to_owned());
self.stripped_members.remove(room);
.insert(event.state_key().to_owned(), event.membership().clone());
}
}
}
@@ -324,43 +281,10 @@ impl MemoryStore {
}
};
match event.content.membership {
MembershipState::Join => {
self.stripped_joined_user_ids
.entry(room.clone())
.or_default()
.insert(event.state_key.clone());
self.stripped_invited_user_ids
.entry(room.clone())
.or_default()
.remove(&event.state_key);
}
MembershipState::Invite => {
self.stripped_invited_user_ids
.entry(room.clone())
.or_default()
.insert(event.state_key.clone());
self.stripped_joined_user_ids
.entry(room.clone())
.or_default()
.remove(&event.state_key);
}
_ => {
self.stripped_joined_user_ids
.entry(room.clone())
.or_default()
.remove(&event.state_key);
self.stripped_invited_user_ids
.entry(room.clone())
.or_default()
.remove(&event.state_key);
}
}
self.stripped_members
.entry(room.clone())
.or_default()
.insert(event.state_key.clone());
.insert(event.state_key, event.content.membership.clone());
}
}
}
@@ -507,42 +431,25 @@ impl MemoryStore {
}
}
fn get_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
if let Some(u) = self.stripped_members.get(room_id) {
u.iter().map(|u| u.key().clone()).collect()
} else {
self.members
.get(room_id)
.map(|u| u.iter().map(|u| u.key().clone()).collect())
.unwrap_or_default()
}
}
/// Get the user IDs for the given room with the given memberships and
/// stripped state.
///
/// If `memberships` is empty, returns all user IDs in the room with the
/// given stripped state.
fn get_user_ids_inner(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
stripped: bool,
) -> Vec<OwnedUserId> {
let map = if stripped { &self.stripped_members } else { &self.members };
fn get_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.invited_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
.unwrap_or_default()
}
fn get_joined_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.joined_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
.unwrap_or_default()
}
fn get_stripped_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.stripped_invited_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
.unwrap_or_default()
}
fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.stripped_joined_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
map.get(room_id)
.map(|u| {
u.iter()
.filter_map(|u| memberships.matches(u.value()).then(|| u.key().clone()))
.collect()
})
.unwrap_or_default()
}
@@ -631,11 +538,9 @@ impl MemoryStore {
}
async fn remove_room(&self, room_id: &RoomId) -> Result<()> {
self.members.remove(room_id);
self.profiles.remove(room_id);
self.display_names.remove(room_id);
self.joined_user_ids.remove(room_id);
self.invited_user_ids.remove(room_id);
self.members.remove(room_id);
self.room_info.remove(room_id);
self.room_state.remove(room_id);
self.room_account_data.remove(room_id);
@@ -711,24 +616,24 @@ impl StateStore for MemoryStore {
self.get_member_event(room_id, state_key).await
}
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok(self.get_user_ids(room_id))
async fn get_user_ids(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
) -> Result<Vec<OwnedUserId>> {
let v = self.get_user_ids_inner(room_id, memberships, true);
if !v.is_empty() {
return Ok(v);
}
Ok(self.get_user_ids_inner(room_id, memberships, false))
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let v = self.get_stripped_invited_user_ids(room_id);
if !v.is_empty() {
return Ok(v);
}
Ok(self.get_invited_user_ids(room_id))
StateStore::get_user_ids(self, room_id, RoomMemberships::INVITE).await
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let v = self.get_stripped_joined_user_ids(room_id);
if !v.is_empty() {
return Ok(v);
}
Ok(self.get_joined_user_ids(room_id))
StateStore::get_user_ids(self, room_id, RoomMemberships::JOIN).await
}
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>> {

View File

@@ -33,6 +33,7 @@ use ruma::{
use super::{StateChanges, StoreError};
use crate::{
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent, RoomInfo,
RoomMemberships,
};
/// An abstract state store trait that can be used to implement different stores
@@ -142,17 +143,23 @@ pub trait StateStore: AsyncTraitDeps {
state_key: &UserId,
) -> Result<Option<RawMemberEvent>, Self::Error>;
/// Get all the user ids of members for a given room, for stripped and
/// regular rooms alike.
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
/// Get the user ids of members for a given room with the given memberships,
/// for stripped and regular rooms alike.
async fn get_user_ids(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
) -> Result<Vec<OwnedUserId>, Self::Error>;
/// Get all the user ids of members that are in the invited state for a
/// given room, for stripped and regular rooms alike.
#[deprecated = "Use get_user_ids with RoomMemberships::INVITE instead."]
async fn get_invited_user_ids(&self, room_id: &RoomId)
-> Result<Vec<OwnedUserId>, Self::Error>;
/// Get all the user ids of members that are in the joined state for a
/// given room, for stripped and regular rooms alike.
#[deprecated = "Use get_user_ids with RoomMemberships::JOIN instead."]
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
/// Get all the pure `RoomInfo`s the store knows about.
@@ -391,19 +398,23 @@ impl<T: StateStore> StateStore for EraseStateStoreError<T> {
self.0.get_member_event(room_id, state_key).await.map_err(Into::into)
}
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
self.0.get_user_ids(room_id).await.map_err(Into::into)
async fn get_user_ids(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
) -> Result<Vec<OwnedUserId>, Self::Error> {
self.0.get_user_ids(room_id, memberships).await.map_err(Into::into)
}
async fn get_invited_user_ids(
&self,
room_id: &RoomId,
) -> Result<Vec<OwnedUserId>, Self::Error> {
self.0.get_invited_user_ids(room_id).await.map_err(Into::into)
self.0.get_user_ids(room_id, RoomMemberships::INVITE).await.map_err(Into::into)
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
self.0.get_joined_user_ids(room_id).await.map_err(Into::into)
self.0.get_user_ids(room_id, RoomMemberships::JOIN).await.map_err(Into::into)
}
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error> {

View File

@@ -35,11 +35,12 @@ use wasm_bindgen::JsValue;
use web_sys::IdbTransactionMode;
use super::{
deserialize_event, encode_key, encode_to_range, keys, serialize_event, Result, ALL_STORES,
deserialize_event, encode_key, encode_to_range, keys, serialize_event, Result, RoomMember,
ALL_STORES,
};
use crate::IndexeddbStateStoreError;
const CURRENT_DB_VERSION: u32 = 5;
const CURRENT_DB_VERSION: u32 = 6;
const CURRENT_META_DB_VERSION: u32 = 2;
/// Sometimes Migrations can't proceed without having to drop existing
@@ -65,6 +66,10 @@ mod old_keys {
pub const SYNC_TOKEN: &str = "sync_token";
pub const MEMBERS: &str = "members";
pub const STRIPPED_MEMBERS: &str = "stripped_members";
pub const JOINED_USER_IDS: &str = "joined_user_ids";
pub const INVITED_USER_IDS: &str = "invited_user_ids";
pub const STRIPPED_JOINED_USER_IDS: &str = "stripped_joined_user_ids";
pub const STRIPPED_INVITED_USER_IDS: &str = "stripped_invited_user_ids";
}
pub async fn upgrade_meta_db(
@@ -211,6 +216,9 @@ pub async fn upgrade_inner_db(
if old_version < 5 {
migration.merge(migrate_to_v5(&pre_db, store_cipher).await?);
}
if old_version < 6 {
migration.merge(migrate_to_v6(&pre_db, store_cipher).await?);
}
}
pre_db.close();
@@ -255,8 +263,8 @@ pub const V1_STORES: &[&str] = &[
old_keys::MEMBERS,
keys::PROFILES,
keys::DISPLAY_NAMES,
keys::JOINED_USER_IDS,
keys::INVITED_USER_IDS,
old_keys::JOINED_USER_IDS,
old_keys::INVITED_USER_IDS,
keys::ROOM_STATE,
keys::ROOM_INFOS,
keys::PRESENCE,
@@ -264,8 +272,8 @@ pub const V1_STORES: &[&str] = &[
keys::STRIPPED_ROOM_INFOS,
old_keys::STRIPPED_MEMBERS,
keys::STRIPPED_ROOM_STATE,
keys::STRIPPED_JOINED_USER_IDS,
keys::STRIPPED_INVITED_USER_IDS,
old_keys::STRIPPED_JOINED_USER_IDS,
old_keys::STRIPPED_INVITED_USER_IDS,
keys::ROOM_USER_RECEIPTS,
keys::ROOM_EVENT_RECEIPTS,
keys::MEDIA,
@@ -499,6 +507,97 @@ async fn migrate_to_v5(
})
}
/// Remove the old user IDs stores and populate the new ones.
async fn migrate_to_v6(
db: &IdbDatabase,
store_cipher: Option<&StoreCipher>,
) -> Result<OngoingMigration> {
// We only have joined and invited user IDs in the old store, so instead we will
// use the room member events to populate the new store.
let tx = db.transaction_on_multi_with_mode(
&[keys::ROOM_STATE, keys::ROOM_INFOS, keys::STRIPPED_ROOM_STATE, keys::STRIPPED_ROOM_INFOS],
IdbTransactionMode::Readonly,
)?;
let state_store = tx.object_store(keys::ROOM_STATE)?;
let room_infos = tx
.object_store(keys::ROOM_INFOS)?
.get_all()?
.await?
.iter()
.filter_map(|f| deserialize_event::<RoomInfo>(store_cipher, f).ok())
.collect::<Vec<_>>();
let mut values = Vec::new();
for room_info in room_infos {
let room_id = room_info.room_id();
let range =
encode_to_range(store_cipher, keys::ROOM_STATE, (room_id, StateEventType::RoomMember))?;
for value in state_store.get_all_with_key(&range)?.await?.iter() {
let member_event =
deserialize_event::<Raw<SyncRoomMemberEvent>>(store_cipher, value.clone())?
.deserialize()?;
let key = encode_key(store_cipher, keys::USER_IDS, (room_id, member_event.state_key()));
let value = serialize_event(store_cipher, &RoomMember::from(&member_event))?;
values.push((key, value));
}
}
let stripped_state_store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
let stripped_room_infos = tx
.object_store(keys::STRIPPED_ROOM_INFOS)?
.get_all()?
.await?
.iter()
.filter_map(|f| deserialize_event::<RoomInfo>(store_cipher, f).ok())
.collect::<Vec<_>>();
let mut stripped_values = Vec::new();
for room_info in stripped_room_infos {
let room_id = room_info.room_id();
let range = encode_to_range(
store_cipher,
keys::STRIPPED_ROOM_STATE,
(room_id, StateEventType::RoomMember),
)?;
for value in stripped_state_store.get_all_with_key(&range)?.await?.iter() {
let stripped_member_event =
deserialize_event::<Raw<StrippedRoomMemberEvent>>(store_cipher, value.clone())?
.deserialize()?;
let key = encode_key(
store_cipher,
keys::STRIPPED_USER_IDS,
(room_id, &stripped_member_event.state_key),
);
let value = serialize_event(store_cipher, &RoomMember::from(&stripped_member_event))?;
stripped_values.push((key, value));
}
}
tx.await.into_result()?;
let mut data = HashMap::new();
if !values.is_empty() {
data.insert(keys::USER_IDS, values);
}
if !stripped_values.is_empty() {
data.insert(keys::STRIPPED_USER_IDS, stripped_values);
}
Ok(OngoingMigration {
drop_stores: HashSet::from_iter([
old_keys::JOINED_USER_IDS,
old_keys::INVITED_USER_IDS,
old_keys::STRIPPED_JOINED_USER_IDS,
old_keys::STRIPPED_INVITED_USER_IDS,
]),
create_stores: HashSet::from_iter([keys::USER_IDS, keys::STRIPPED_USER_IDS]),
data,
})
}
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
@@ -506,8 +605,8 @@ mod tests {
use assert_matches::assert_matches;
use indexed_db_futures::prelude::*;
use matrix_sdk_base::{
deserialized_responses::RawMemberEvent, RoomInfo, RoomState, StateStore, StateStoreDataKey,
StoreError,
deserialized_responses::RawMemberEvent, RoomInfo, RoomMemberships, RoomState, StateStore,
StateStoreDataKey, StoreError,
};
use matrix_sdk_test::{async_test, test_json};
use ruma::{
@@ -523,12 +622,10 @@ mod tests {
use uuid::Uuid;
use wasm_bindgen::JsValue;
use super::{
old_keys, MigrationConflictStrategy, CURRENT_DB_VERSION, CURRENT_META_DB_VERSION, V1_STORES,
};
use super::{old_keys, MigrationConflictStrategy, CURRENT_DB_VERSION, CURRENT_META_DB_VERSION};
use crate::{
safe_encode::SafeEncode,
state_store::{encode_key, keys, serialize_event, Result, ALL_STORES},
state_store::{encode_key, keys, serialize_event, Result},
IndexeddbStateStore, IndexeddbStateStoreError,
};
@@ -541,20 +638,53 @@ mod tests {
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
let db = evt.db();
// Initialize stores.
if version < 4 {
for name in V1_STORES {
db.create_object_store(name)?;
}
} else {
for name in ALL_STORES {
db.create_object_store(name)?;
}
// Stores common to all versions.
let common_stores = &[
keys::ACCOUNT_DATA,
keys::PROFILES,
keys::DISPLAY_NAMES,
keys::ROOM_STATE,
keys::ROOM_INFOS,
keys::PRESENCE,
keys::ROOM_ACCOUNT_DATA,
keys::STRIPPED_ROOM_INFOS,
keys::STRIPPED_ROOM_STATE,
keys::ROOM_USER_RECEIPTS,
keys::ROOM_EVENT_RECEIPTS,
keys::MEDIA,
keys::CUSTOM,
];
if version < 5 {
for name in [old_keys::MEMBERS, old_keys::STRIPPED_MEMBERS] {
db.create_object_store(name)?;
}
for name in common_stores {
db.create_object_store(name)?;
}
if version < 4 {
for name in [old_keys::SYNC_TOKEN, old_keys::SESSION] {
db.create_object_store(name)?;
}
}
if version >= 4 {
db.create_object_store(keys::KV)?;
}
if version < 5 {
for name in [old_keys::MEMBERS, old_keys::STRIPPED_MEMBERS] {
db.create_object_store(name)?;
}
}
if version < 6 {
for name in [
old_keys::INVITED_USER_IDS,
old_keys::JOINED_USER_IDS,
old_keys::STRIPPED_INVITED_USER_IDS,
old_keys::STRIPPED_JOINED_USER_IDS,
] {
db.create_object_store(name)?;
}
}
if version >= 6 {
for name in [keys::USER_IDS, keys::STRIPPED_USER_IDS] {
db.create_object_store(name)?;
}
}
@@ -924,4 +1054,145 @@ mod tests {
Ok(())
}
#[async_test]
pub async fn test_migrating_to_v6() -> Result<()> {
let name = format!("migrating-v6-{}", Uuid::new_v4().as_hyphenated().to_string());
let room_id = room_id!("!room:localhost");
let invite_member_event =
Raw::new(&*test_json::MEMBER_INVITE).unwrap().cast::<SyncRoomMemberEvent>();
let invite_user_id = user_id!("@invited:localhost");
let ban_member_event =
Raw::new(&*test_json::MEMBER_BAN).unwrap().cast::<SyncRoomMemberEvent>();
let ban_user_id = user_id!("@banned:localhost");
let stripped_room_id = room_id!("!stripped_room:localhost");
let stripped_member_event =
Raw::new(&*test_json::MEMBER_STRIPPED).unwrap().cast::<StrippedRoomMemberEvent>();
let stripped_user_id = user_id!("@example:localhost");
// Populate DB with old table.
{
let db = create_fake_db(&name, 5).await?;
let tx = db.transaction_on_multi_with_mode(
&[
keys::ROOM_STATE,
keys::ROOM_INFOS,
keys::STRIPPED_ROOM_STATE,
keys::STRIPPED_ROOM_INFOS,
old_keys::INVITED_USER_IDS,
old_keys::JOINED_USER_IDS,
old_keys::STRIPPED_INVITED_USER_IDS,
old_keys::STRIPPED_JOINED_USER_IDS,
],
IdbTransactionMode::Readwrite,
)?;
let state_store = tx.object_store(keys::ROOM_STATE)?;
state_store.put_key_val(
&encode_key(
None,
keys::ROOM_STATE,
(room_id, StateEventType::RoomMember, invite_user_id),
),
&serialize_event(None, &invite_member_event)?,
)?;
state_store.put_key_val(
&encode_key(
None,
keys::ROOM_STATE,
(room_id, StateEventType::RoomMember, ban_user_id),
),
&serialize_event(None, &ban_member_event)?,
)?;
let room_infos_store = tx.object_store(keys::ROOM_INFOS)?;
let room_info = RoomInfo::new(room_id, RoomState::Joined);
room_infos_store.put_key_val(
&encode_key(None, keys::ROOM_INFOS, room_id),
&serialize_event(None, &room_info)?,
)?;
let stripped_state_store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
stripped_state_store.put_key_val(
&encode_key(
None,
keys::STRIPPED_ROOM_STATE,
(stripped_room_id, StateEventType::RoomMember, stripped_user_id),
),
&serialize_event(None, &stripped_member_event)?,
)?;
let stripped_room_infos_store = tx.object_store(keys::STRIPPED_ROOM_INFOS)?;
let stripped_room_info = RoomInfo::new(stripped_room_id, RoomState::Invited);
stripped_room_infos_store.put_key_val(
&encode_key(None, keys::STRIPPED_ROOM_INFOS, stripped_room_id),
&serialize_event(None, &stripped_room_info)?,
)?;
// Populate the old user IDs stores to check the data is not reused.
let joined_user_id = user_id!("@joined_user:localhost");
tx.object_store(old_keys::JOINED_USER_IDS)?.put_key_val(
&encode_key(None, old_keys::JOINED_USER_IDS, (room_id, joined_user_id)),
&serialize_event(None, &joined_user_id)?,
)?;
let invited_user_id = user_id!("@invited_user:localhost");
tx.object_store(old_keys::INVITED_USER_IDS)?.put_key_val(
&encode_key(None, old_keys::INVITED_USER_IDS, (room_id, invited_user_id)),
&serialize_event(None, &invited_user_id)?,
)?;
let stripped_joined_user_id = user_id!("@stripped_joined_user:localhost");
tx.object_store(old_keys::STRIPPED_JOINED_USER_IDS)?.put_key_val(
&encode_key(
None,
old_keys::STRIPPED_JOINED_USER_IDS,
(room_id, stripped_joined_user_id),
),
&serialize_event(None, &stripped_joined_user_id)?,
)?;
let stripped_invited_user_id = user_id!("@stripped_invited_user:localhost");
tx.object_store(old_keys::STRIPPED_INVITED_USER_IDS)?.put_key_val(
&encode_key(
None,
old_keys::STRIPPED_INVITED_USER_IDS,
(room_id, stripped_invited_user_id),
),
&serialize_event(None, &stripped_invited_user_id)?,
)?;
tx.await.into_result()?;
db.close();
}
// this transparently migrates to the latest version
let store = IndexeddbStateStore::builder().name(name).build().await?;
assert_eq!(store.get_user_ids(room_id, RoomMemberships::JOIN).await.unwrap().len(), 0);
assert_eq!(
store.get_user_ids(room_id, RoomMemberships::INVITE).await.unwrap().as_slice(),
[invite_user_id.to_owned()]
);
let user_ids = store.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
assert_eq!(user_ids.len(), 2);
assert!(user_ids.contains(&invite_user_id.to_owned()));
assert!(user_ids.contains(&ban_user_id.to_owned()));
assert_eq!(
store.get_user_ids(stripped_room_id, RoomMemberships::JOIN).await.unwrap().as_slice(),
[stripped_user_id.to_owned()]
);
assert_eq!(
store.get_user_ids(stripped_room_id, RoomMemberships::INVITE).await.unwrap().len(),
0
);
assert_eq!(
store
.get_user_ids(stripped_room_id, RoomMemberships::empty())
.await
.unwrap()
.as_slice(),
[stripped_user_id.to_owned()]
);
Ok(())
}
}

View File

@@ -25,7 +25,7 @@ use matrix_sdk_base::{
deserialized_responses::RawMemberEvent,
media::{MediaRequest, UniqueKey},
store::{StateChanges, StateStore, StoreError},
MinimalStateEvent, RoomInfo, StateStoreDataKey, StateStoreDataValue,
MinimalStateEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
};
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
use ruma::{
@@ -37,12 +37,12 @@ use ruma::{
MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent, SyncRoomMemberEvent,
},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, SyncStateEvent,
},
serde::Raw,
CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId,
};
use serde::{de::DeserializeOwned, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tracing::{debug, warn};
use wasm_bindgen::JsValue;
use web_sys::IdbKeyRange;
@@ -96,8 +96,7 @@ mod keys {
pub const PROFILES: &str = "profiles";
pub const DISPLAY_NAMES: &str = "display_names";
pub const JOINED_USER_IDS: &str = "joined_user_ids";
pub const INVITED_USER_IDS: &str = "invited_user_ids";
pub const USER_IDS: &str = "user_ids";
pub const ROOM_STATE: &str = "room_state";
pub const ROOM_INFOS: &str = "room_infos";
@@ -106,8 +105,7 @@ mod keys {
pub const STRIPPED_ROOM_INFOS: &str = "stripped_room_infos";
pub const STRIPPED_ROOM_STATE: &str = "stripped_room_state";
pub const STRIPPED_JOINED_USER_IDS: &str = "stripped_joined_user_ids";
pub const STRIPPED_INVITED_USER_IDS: &str = "stripped_invited_user_ids";
pub const STRIPPED_USER_IDS: &str = "stripped_user_ids";
pub const ROOM_USER_RECEIPTS: &str = "room_user_receipts";
pub const ROOM_EVENT_RECEIPTS: &str = "room_event_receipts";
@@ -122,16 +120,14 @@ mod keys {
ACCOUNT_DATA,
PROFILES,
DISPLAY_NAMES,
JOINED_USER_IDS,
INVITED_USER_IDS,
USER_IDS,
ROOM_STATE,
ROOM_INFOS,
PRESENCE,
ROOM_ACCOUNT_DATA,
STRIPPED_ROOM_INFOS,
STRIPPED_ROOM_STATE,
STRIPPED_JOINED_USER_IDS,
STRIPPED_INVITED_USER_IDS,
STRIPPED_USER_IDS,
ROOM_USER_RECEIPTS,
ROOM_EVENT_RECEIPTS,
MEDIA,
@@ -316,85 +312,52 @@ impl IndexeddbStateStore {
encode_to_range(self.store_cipher.as_deref(), table_name, key)
}
pub async fn get_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok([
self.get_invited_user_ids_inner(room_id).await?,
self.get_joined_user_ids_inner(room_id).await?,
]
.concat())
}
pub async fn get_invited_user_ids_inner(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(keys::INVITED_USER_IDS, room_id)?;
let entries = self
.inner
.transaction_on_one_with_mode(keys::INVITED_USER_IDS, IdbTransactionMode::Readonly)?
.object_store(keys::INVITED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>();
Ok(entries)
}
pub async fn get_joined_user_ids_inner(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(keys::JOINED_USER_IDS, room_id)?;
Ok(self
.inner
.transaction_on_one_with_mode(keys::JOINED_USER_IDS, IdbTransactionMode::Readonly)?
.object_store(keys::JOINED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>())
}
pub async fn get_stripped_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok([
self.get_stripped_invited_user_ids(room_id).await?,
self.get_stripped_joined_user_ids(room_id).await?,
]
.concat())
}
pub async fn get_stripped_invited_user_ids(
/// Get user IDs for the given room with the given memberships and stripped
/// state.
pub async fn get_user_ids_inner(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
stripped: bool,
) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(keys::STRIPPED_INVITED_USER_IDS, room_id)?;
let entries = self
.inner
.transaction_on_one_with_mode(
keys::STRIPPED_INVITED_USER_IDS,
IdbTransactionMode::Readonly,
)?
.object_store(keys::STRIPPED_INVITED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>();
let store_name = if stripped { keys::STRIPPED_USER_IDS } else { keys::USER_IDS };
Ok(entries)
}
let tx =
self.inner.transaction_on_one_with_mode(store_name, IdbTransactionMode::Readonly)?;
let store = tx.object_store(store_name)?;
let range = self.encode_to_range(store_name, room_id)?;
pub async fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(keys::STRIPPED_JOINED_USER_IDS, room_id)?;
Ok(self
.inner
.transaction_on_one_with_mode(
keys::STRIPPED_JOINED_USER_IDS,
IdbTransactionMode::Readonly,
)?
.object_store(keys::STRIPPED_JOINED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>())
let user_ids = if memberships.is_empty() {
// It should be faster to just get all user IDs in this case.
store
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<RoomMember>(f).ok().map(|m| m.user_id))
.collect::<Vec<_>>()
} else {
let mut user_ids = Vec::new();
let cursor = store.open_cursor_with_range(&range)?.await?;
if let Some(cursor) = cursor {
loop {
let value = cursor.value();
let member = self.deserialize_event::<RoomMember>(value)?;
if memberships.matches(&member.membership) {
user_ids.push(member.user_id);
}
if !cursor.continue_cursor()?.await? {
break;
}
}
}
user_ids
};
Ok(user_ids)
}
async fn get_custom_value_for_js(&self, jskey: &JsValue) -> Result<Option<Vec<u8>>> {
@@ -543,10 +506,8 @@ impl_state_store! {
if !changes.state.is_empty() {
stores.extend([
keys::ROOM_STATE,
keys::INVITED_USER_IDS,
keys::JOINED_USER_IDS,
keys::STRIPPED_INVITED_USER_IDS,
keys::STRIPPED_JOINED_USER_IDS,
keys::USER_IDS,
keys::STRIPPED_USER_IDS,
keys::STRIPPED_ROOM_STATE,
keys::PROFILES,
]);
@@ -563,8 +524,7 @@ impl_state_store! {
if !changes.stripped_state.is_empty() {
stores.extend([
keys::STRIPPED_ROOM_STATE,
keys::STRIPPED_INVITED_USER_IDS,
keys::STRIPPED_JOINED_USER_IDS,
keys::STRIPPED_USER_IDS,
]);
}
@@ -622,11 +582,9 @@ impl_state_store! {
if !changes.state.is_empty() {
let state = tx.object_store(keys::ROOM_STATE)?;
let profiles = tx.object_store(keys::PROFILES)?;
let joined = tx.object_store(keys::JOINED_USER_IDS)?;
let invited = tx.object_store(keys::INVITED_USER_IDS)?;
let user_ids = tx.object_store(keys::USER_IDS)?;
let stripped_state = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
let stripped_joined = tx.object_store(keys::STRIPPED_JOINED_USER_IDS)?;
let stripped_invited = tx.object_store(keys::STRIPPED_INVITED_USER_IDS)?;
let stripped_user_ids = tx.object_store(keys::STRIPPED_USER_IDS)?;
for (room, event_types) in &changes.state {
let profile_changes = changes.profiles.get(room);
@@ -650,31 +608,13 @@ impl_state_store! {
let key = (room, state_key);
stripped_joined
.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
stripped_invited
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
stripped_user_ids
.delete(&self.encode_key(keys::STRIPPED_USER_IDS, key))?;
match event.membership() {
MembershipState::Join => {
joined.put_key_val_owned(
&self.encode_key(keys::JOINED_USER_IDS, key),
&self.serialize_event(state_key)?,
user_ids.put_key_val_owned(
&self.encode_key(keys::USER_IDS, key),
&self.serialize_event(&RoomMember::from(&event))?,
)?;
invited.delete(&self.encode_key(keys::INVITED_USER_IDS, key))?;
}
MembershipState::Invite => {
invited.put_key_val_owned(
&self.encode_key(keys::INVITED_USER_IDS, key),
&self.serialize_event(state_key)?,
)?;
joined.delete(&self.encode_key(keys::JOINED_USER_IDS, key))?;
}
_ => {
joined.delete(&self.encode_key(keys::JOINED_USER_IDS, key))?;
invited.delete(&self.encode_key(keys::INVITED_USER_IDS, key))?;
}
}
if let Some(profile) = profile_changes.and_then(|p| p.get(event.state_key())) {
profiles.put_key_val_owned(
@@ -724,8 +664,7 @@ impl_state_store! {
if !changes.stripped_state.is_empty() {
let store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
let joined = tx.object_store(keys::STRIPPED_JOINED_USER_IDS)?;
let invited = tx.object_store(keys::STRIPPED_INVITED_USER_IDS)?;
let user_ids = tx.object_store(keys::STRIPPED_USER_IDS)?;
for (room, event_types) in &changes.stripped_state {
for (event_type, events) in event_types {
@@ -735,42 +674,24 @@ impl_state_store! {
store.put_key_val(&key, &self.serialize_event(&raw_event)?)?;
if *event_type == StateEventType::RoomMember {
let event = match raw_event.deserialize_as::<StrippedRoomMemberEvent>() {
Ok(ev) => ev,
Err(e) => {
let event_id: Option<String> =
raw_event.get_field("event_id").ok().flatten();
debug!(event_id, "Failed to deserialize stripped member event: {e}");
continue;
}
};
let event = match raw_event.deserialize_as::<StrippedRoomMemberEvent>() {
Ok(ev) => ev,
Err(e) => {
let event_id: Option<String> =
raw_event.get_field("event_id").ok().flatten();
debug!(event_id, "Failed to deserialize stripped member event: {e}");
continue;
}
};
let key = (room, state_key);
let key = (room, state_key);
match event.content.membership {
MembershipState::Join => {
joined.put_key_val_owned(
&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key),
&self.serialize_event(state_key)?,
)?;
invited
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
}
MembershipState::Invite => {
invited.put_key_val_owned(
&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key),
&self.serialize_event(state_key)?,
)?;
joined.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
}
_ => {
joined.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
invited
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
}
user_ids.put_key_val_owned(
&self.encode_key(keys::STRIPPED_USER_IDS, key),
&self.serialize_event(&RoomMember::from(&event))?,
)?;
}
}
}
}
}
}
@@ -1176,13 +1097,13 @@ impl_state_store! {
let prefixed_stores = [
keys::PROFILES,
keys::DISPLAY_NAMES,
keys::INVITED_USER_IDS,
keys::JOINED_USER_IDS,
keys::USER_IDS,
keys::ROOM_STATE,
keys::ROOM_ACCOUNT_DATA,
keys::ROOM_EVENT_RECEIPTS,
keys::ROOM_USER_RECEIPTS,
keys::STRIPPED_ROOM_STATE,
keys::STRIPPED_USER_IDS,
];
let all_stores = {
@@ -1210,28 +1131,39 @@ impl_state_store! {
tx.await.into_result().map_err(|e| e.into())
}
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let ids: Vec<OwnedUserId> = self.get_stripped_user_ids_stream(room_id).await?;
async fn get_user_ids(&self, room_id: &RoomId, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
let ids = self.get_user_ids_inner(room_id, memberships, true).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_user_ids_stream(room_id).await
self.get_user_ids_inner(room_id, memberships, false).await
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let ids: Vec<OwnedUserId> = self.get_stripped_invited_user_ids(room_id).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_invited_user_ids_inner(room_id).await
self.get_user_ids(room_id, RoomMemberships::INVITE).await
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let ids: Vec<OwnedUserId> = self.get_stripped_joined_user_ids(room_id).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_joined_user_ids_inner(room_id).await
self.get_user_ids(room_id, RoomMemberships::JOIN).await
}
}
/// A room member.
#[derive(Debug, Serialize, Deserialize)]
struct RoomMember {
user_id: OwnedUserId,
membership: MembershipState,
}
impl From<&SyncStateEvent<RoomMemberEventContent>> for RoomMember {
fn from(event: &SyncStateEvent<RoomMemberEventContent>) -> Self {
Self { user_id: event.state_key().clone(), membership: event.membership().clone() }
}
}
impl From<&StrippedRoomMemberEvent> for RoomMember {
fn from(event: &StrippedRoomMemberEvent) -> Self {
Self { user_id: event.state_key.clone(), membership: event.content.membership.clone() }
}
}

View File

@@ -27,10 +27,10 @@ use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
use sled::{transaction::TransactionError, Batch, Transactional, Tree};
use tracing::debug;
use super::{keys, Result, SledStateStore, SledStoreError};
use super::{keys, Result, RoomMember, SledStateStore, SledStoreError};
use crate::encode_key::EncodeKey;
const DATABASE_VERSION: u8 = 5;
const DATABASE_VERSION: u8 = 7;
const VERSION_KEY: &str = "state-store-version";
@@ -87,6 +87,13 @@ impl SledStateStore {
return Ok(());
}
// Version 6 was dropped and migration is similar to v7.
if old_version < 7 {
self.migrate_to_v7()?;
return Ok(());
}
// FUTURE UPGRADE CODE GOES HERE
// can't upgrade from that version to the new one
@@ -270,6 +277,82 @@ impl SledStateStore {
self.set_db_version(5)
}
/// Remove the old user IDs stores and populate the new ones.
fn migrate_to_v7(&self) -> Result<()> {
{
// Reset v6 stores.
self.user_ids.clear()?;
self.stripped_user_ids.clear()?;
// We only have joined and invited user IDs in the old stores, so instead we
// use the room member events to populate the new stores.
let state = &self.inner.open_tree(keys::ROOM_STATE)?;
let mut user_ids_batch = sled::Batch::default();
for room_info in
self.room_info.iter().map(|r| self.deserialize_value::<RoomInfo>(&r?.1))
{
let room_info = room_info?;
let room_id = room_info.room_id();
let prefix =
self.encode_key(keys::ROOM_STATE, (room_id, StateEventType::RoomMember));
for entry in state.scan_prefix(prefix) {
let (_, value) = entry?;
let member_event = self
.deserialize_value::<Raw<SyncRoomMemberEvent>>(&value)?
.deserialize()?;
let key = self.encode_key(keys::USER_ID, (room_id, member_event.state_key()));
let value = self.serialize_value(&RoomMember::from(&member_event))?;
user_ids_batch.insert(key, value);
}
}
let stripped_state = &self.inner.open_tree(keys::STRIPPED_ROOM_STATE)?;
let mut stripped_user_ids_batch = sled::Batch::default();
for room_info in
self.stripped_room_infos.iter().map(|r| self.deserialize_value::<RoomInfo>(&r?.1))
{
let room_info = room_info?;
let room_id = room_info.room_id();
let prefix = self
.encode_key(keys::STRIPPED_ROOM_STATE, (room_id, StateEventType::RoomMember));
for entry in stripped_state.scan_prefix(prefix) {
let (_, value) = entry?;
let stripped_member_event = self
.deserialize_value::<Raw<StrippedRoomMemberEvent>>(&value)?
.deserialize()?;
let key = self.encode_key(
keys::STRIPPED_USER_ID,
(room_id, &stripped_member_event.state_key),
);
let value = self.serialize_value(&RoomMember::from(&stripped_member_event))?;
stripped_user_ids_batch.insert(key, value);
}
}
let ret: Result<(), TransactionError<SledStoreError>> =
(&self.user_ids, &self.stripped_user_ids).transaction(
|(user_ids, stripped_user_ids)| {
user_ids.apply_batch(&user_ids_batch)?;
stripped_user_ids.apply_batch(&stripped_user_ids_batch)?;
Ok(())
},
);
ret?;
}
self.inner.drop_tree(old_keys::JOINED_USER_ID)?;
self.inner.drop_tree(old_keys::INVITED_USER_ID)?;
self.inner.drop_tree(old_keys::STRIPPED_JOINED_USER_ID)?;
self.inner.drop_tree(old_keys::STRIPPED_INVITED_USER_ID)?;
self.set_db_version(7)
}
}
mod old_keys {
@@ -278,14 +361,18 @@ mod old_keys {
pub const SESSION: &str = "session";
pub const MEMBER: &str = "member";
pub const STRIPPED_ROOM_MEMBER: &str = "stripped-room-member";
pub const INVITED_USER_ID: &str = "invited-user-id";
pub const JOINED_USER_ID: &str = "joined-user-id";
pub const STRIPPED_INVITED_USER_ID: &str = "stripped-invited-user-id";
pub const STRIPPED_JOINED_USER_ID: &str = "stripped-joined-user-id";
}
pub const V1_DB_STORES: &[&str] = &[
keys::ACCOUNT_DATA,
old_keys::SYNC_TOKEN,
keys::DISPLAY_NAME,
keys::INVITED_USER_ID,
keys::JOINED_USER_ID,
old_keys::INVITED_USER_ID,
old_keys::JOINED_USER_ID,
keys::MEDIA,
old_keys::MEMBER,
keys::PRESENCE,
@@ -297,8 +384,8 @@ pub const V1_DB_STORES: &[&str] = &[
keys::ROOM_USER_RECEIPT,
keys::ROOM,
old_keys::SESSION,
keys::STRIPPED_INVITED_USER_ID,
keys::STRIPPED_JOINED_USER_ID,
old_keys::STRIPPED_INVITED_USER_ID,
old_keys::STRIPPED_JOINED_USER_ID,
keys::STRIPPED_ROOM_INFO,
old_keys::STRIPPED_ROOM_MEMBER,
keys::STRIPPED_ROOM_STATE,
@@ -309,7 +396,8 @@ pub const V1_DB_STORES: &[&str] = &[
mod test {
use assert_matches::assert_matches;
use matrix_sdk_base::{
deserialized_responses::RawMemberEvent, RoomInfo, RoomState, StateStoreDataKey,
deserialized_responses::RawMemberEvent, RoomInfo, RoomMemberships, RoomState,
StateStoreDataKey,
};
use matrix_sdk_test::{async_test, test_json};
use ruma::{
@@ -613,4 +701,122 @@ mod test {
);
assert_eq!(stored_stripped_member_event.json().get(), stripped_member_event.json().get());
}
#[async_test]
pub async fn migrating_v5_to_v7() {
let room_id = room_id!("!room:localhost");
let invite_member_event =
Raw::new(&*test_json::MEMBER_INVITE).unwrap().cast::<SyncRoomMemberEvent>();
let invite_user_id = user_id!("@invited:localhost");
let ban_member_event =
Raw::new(&*test_json::MEMBER_BAN).unwrap().cast::<SyncRoomMemberEvent>();
let ban_user_id = user_id!("@banned:localhost");
let stripped_room_id = room_id!("!stripped_room:localhost");
let stripped_member_event =
Raw::new(&*test_json::MEMBER_STRIPPED).unwrap().cast::<StrippedRoomMemberEvent>();
let stripped_user_id = user_id!("@example:localhost");
let folder = TempDir::new().unwrap();
{
let store = SledStateStore::builder()
.path(folder.path().to_path_buf())
.passphrase("secret".to_owned())
.build()
.unwrap();
let state = store.inner.open_tree(keys::ROOM_STATE).unwrap();
state
.insert(
store.encode_key(
keys::ROOM_STATE,
(room_id, StateEventType::RoomMember, invite_user_id),
),
store.serialize_value(&invite_member_event).unwrap(),
)
.unwrap();
state
.insert(
store.encode_key(
keys::ROOM_STATE,
(room_id, StateEventType::RoomMember, ban_user_id),
),
store.serialize_value(&ban_member_event).unwrap(),
)
.unwrap();
let room_infos = store.inner.open_tree(keys::ROOM_INFO).unwrap();
let room_info = RoomInfo::new(room_id, RoomState::Joined);
room_infos
.insert(
store.encode_key(keys::ROOM_INFO, room_id),
store.serialize_value(&room_info).unwrap(),
)
.unwrap();
let stripped_state = store.inner.open_tree(keys::STRIPPED_ROOM_STATE).unwrap();
stripped_state
.insert(
store.encode_key(
keys::STRIPPED_ROOM_STATE,
(stripped_room_id, StateEventType::RoomMember, stripped_user_id),
),
store.serialize_value(&stripped_member_event).unwrap(),
)
.unwrap();
let stripped_room_infos = store.inner.open_tree(keys::STRIPPED_ROOM_INFO).unwrap();
let stripped_room_info = RoomInfo::new(stripped_room_id, RoomState::Invited);
stripped_room_infos
.insert(
store.encode_key(keys::STRIPPED_ROOM_INFO, stripped_room_id),
store.serialize_value(&stripped_room_info).unwrap(),
)
.unwrap();
store.set_db_version(5).unwrap();
}
let store = SledStateStore::builder()
.path(folder.path().to_path_buf())
.passphrase("secret".to_owned())
.build()
.unwrap();
assert_eq!(
store.get_user_ids(room_id, RoomMemberships::JOIN, false).await.unwrap().len(),
0
);
assert_eq!(
store.get_user_ids(room_id, RoomMemberships::INVITE, false).await.unwrap().as_slice(),
[invite_user_id.to_owned()]
);
let user_ids = store.get_user_ids(room_id, RoomMemberships::empty(), false).await.unwrap();
assert_eq!(user_ids.len(), 2);
assert!(user_ids.contains(&invite_user_id.to_owned()));
assert!(user_ids.contains(&ban_user_id.to_owned()));
assert_eq!(
store
.get_user_ids(stripped_room_id, RoomMemberships::JOIN, true)
.await
.unwrap()
.as_slice(),
[stripped_user_id.to_owned()]
);
assert_eq!(
store
.get_user_ids(stripped_room_id, RoomMemberships::INVITE, true)
.await
.unwrap()
.len(),
0
);
assert_eq!(
store
.get_user_ids(stripped_room_id, RoomMemberships::empty(), true)
.await
.unwrap()
.as_slice(),
[stripped_user_id.to_owned()]
);
}
}

View File

@@ -21,12 +21,12 @@ use std::{
use async_trait::async_trait;
use futures_core::stream::Stream;
use futures_util::stream::{self, StreamExt, TryStreamExt};
use futures_util::stream::{self, TryStreamExt};
use matrix_sdk_base::{
deserialized_responses::RawMemberEvent,
media::{MediaRequest, UniqueKey},
store::{Result as StoreResult, StateChanges, StateStore, StoreError},
MinimalStateEvent, RoomInfo, StateStoreDataKey, StateStoreDataValue,
MinimalStateEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
};
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
use ruma::{
@@ -38,13 +38,13 @@ use ruma::{
MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent, SyncRoomMemberEvent,
},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, SyncStateEvent,
},
serde::Raw,
CanonicalJsonObject, EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId,
RoomVersionId, UserId,
};
use serde::{de::DeserializeOwned, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sled::{
transaction::{ConflictableTransactionError, TransactionError},
Config, Db, Transactional, Tree,
@@ -112,8 +112,7 @@ mod keys {
pub const ACCOUNT_DATA: &str = "account-data";
pub const CUSTOM: &str = "custom";
pub const DISPLAY_NAME: &str = "display-name";
pub const INVITED_USER_ID: &str = "invited-user-id";
pub const JOINED_USER_ID: &str = "joined-user-id";
pub const USER_ID: &str = "user-ids";
pub const MEDIA: &str = "media";
pub const PRESENCE: &str = "presence";
pub const PROFILE: &str = "profile";
@@ -123,8 +122,7 @@ mod keys {
pub const ROOM_STATE: &str = "room-state";
pub const ROOM_USER_RECEIPT: &str = "room-user-receipt";
pub const ROOM: &str = "room";
pub const STRIPPED_INVITED_USER_ID: &str = "stripped-invited-user-id";
pub const STRIPPED_JOINED_USER_ID: &str = "stripped-joined-user-id";
pub const STRIPPED_USER_ID: &str = "stripped-user-ids";
pub const STRIPPED_ROOM_INFO: &str = "stripped-room-info";
pub const STRIPPED_ROOM_STATE: &str = "stripped-room-state";
pub const KV: &str = "kv";
@@ -282,13 +280,11 @@ pub struct SledStateStore {
account_data: Tree,
profiles: Tree,
display_names: Tree,
joined_user_ids: Tree,
invited_user_ids: Tree,
user_ids: Tree,
room_info: Tree,
room_state: Tree,
room_account_data: Tree,
stripped_joined_user_ids: Tree,
stripped_invited_user_ids: Tree,
stripped_user_ids: Tree,
stripped_room_infos: Tree,
stripped_room_state: Tree,
presence: Tree,
@@ -319,16 +315,14 @@ impl SledStateStore {
let profiles = db.open_tree(keys::PROFILE)?;
let display_names = db.open_tree(keys::DISPLAY_NAME)?;
let joined_user_ids = db.open_tree(keys::JOINED_USER_ID)?;
let invited_user_ids = db.open_tree(keys::INVITED_USER_ID)?;
let user_ids = db.open_tree(keys::USER_ID)?;
let room_state = db.open_tree(keys::ROOM_STATE)?;
let room_info = db.open_tree(keys::ROOM_INFO)?;
let presence = db.open_tree(keys::PRESENCE)?;
let room_account_data = db.open_tree(keys::ROOM_ACCOUNT_DATA)?;
let stripped_joined_user_ids = db.open_tree(keys::STRIPPED_JOINED_USER_ID)?;
let stripped_invited_user_ids = db.open_tree(keys::STRIPPED_INVITED_USER_ID)?;
let stripped_user_ids = db.open_tree(keys::STRIPPED_USER_ID)?;
let stripped_room_infos = db.open_tree(keys::STRIPPED_ROOM_INFO)?;
let stripped_room_state = db.open_tree(keys::STRIPPED_ROOM_STATE)?;
@@ -347,14 +341,12 @@ impl SledStateStore {
account_data,
profiles,
display_names,
joined_user_ids,
invited_user_ids,
user_ids,
room_account_data,
presence,
room_state,
room_info,
stripped_joined_user_ids,
stripped_invited_user_ids,
stripped_user_ids,
stripped_room_infos,
stripped_room_state,
room_user_receipts,
@@ -467,13 +459,11 @@ impl SledStateStore {
let ret: Result<(), TransactionError<SledStoreError>> = (
&self.profiles,
&self.display_names,
&self.joined_user_ids,
&self.invited_user_ids,
&self.user_ids,
&self.room_info,
&self.room_state,
&self.room_account_data,
&self.stripped_joined_user_ids,
&self.stripped_invited_user_ids,
&self.stripped_user_ids,
&self.stripped_room_infos,
&self.stripped_room_state,
)
@@ -481,13 +471,11 @@ impl SledStateStore {
|(
profiles,
display_names,
joined,
invited,
user_ids,
rooms,
state,
room_account_data,
stripped_joined,
stripped_invited,
stripped_user_ids,
stripped_rooms,
stripped_state,
)| {
@@ -546,43 +534,14 @@ impl SledStateStore {
let key = (room, state_key);
stripped_joined.remove(
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
)?;
stripped_invited.remove(
self.encode_key(keys::STRIPPED_INVITED_USER_ID, key),
)?;
stripped_user_ids
.remove(self.encode_key(keys::STRIPPED_USER_ID, key))?;
match event.membership() {
MembershipState::Join => {
joined.insert(
self.encode_key(keys::JOINED_USER_ID, key),
self.serialize_value(state_key)
.map_err(ConflictableTransactionError::Abort)?,
)?;
invited.remove(
self.encode_key(keys::INVITED_USER_ID, key),
)?;
}
MembershipState::Invite => {
invited.insert(
self.encode_key(keys::INVITED_USER_ID, key),
self.serialize_value(state_key)
.map_err(ConflictableTransactionError::Abort)?,
)?;
joined.remove(
self.encode_key(keys::JOINED_USER_ID, key),
)?;
}
_ => {
joined.remove(
self.encode_key(keys::JOINED_USER_ID, key),
)?;
invited.remove(
self.encode_key(keys::INVITED_USER_ID, key),
)?;
}
}
user_ids.insert(
self.encode_key(keys::USER_ID, key),
self.serialize_value(&RoomMember::from(&event))
.map_err(ConflictableTransactionError::Abort)?,
)?;
if let Some(profile) =
profile_changes.and_then(|p| p.get(event.state_key()))
@@ -646,46 +605,11 @@ impl SledStateStore {
};
let key = (room, state_key);
match event.content.membership {
MembershipState::Join => {
stripped_joined.insert(
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
self.serialize_value(state_key)
.map_err(ConflictableTransactionError::Abort)?,
)?;
stripped_invited.remove(
self.encode_key(
keys::STRIPPED_INVITED_USER_ID,
key,
),
)?;
}
MembershipState::Invite => {
stripped_invited.insert(
self.encode_key(
keys::STRIPPED_INVITED_USER_ID,
key,
),
self.serialize_value(state_key)
.map_err(ConflictableTransactionError::Abort)?,
)?;
stripped_joined.remove(
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
)?;
}
_ => {
stripped_joined.remove(
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
)?;
stripped_invited.remove(
self.encode_key(
keys::STRIPPED_INVITED_USER_ID,
key,
),
)?;
}
}
stripped_user_ids.insert(
self.encode_key(keys::STRIPPED_USER_ID, key),
self.serialize_value(&RoomMember::from(&event))
.map_err(ConflictableTransactionError::Abort)?,
)?;
}
}
}
@@ -930,87 +854,34 @@ impl SledStateStore {
.await?
}
pub async fn get_user_ids_stream(
/// Get the user IDs for the given room with the given memberships and
/// stripped state.
pub async fn get_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
Ok(self
.get_joined_user_ids(room_id)
.await?
.chain(self.get_invited_user_ids(room_id).await?))
}
pub async fn get_stripped_user_ids_stream(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
Ok(self
.get_stripped_joined_user_ids(room_id)
.await?
.chain(self.get_stripped_invited_user_ids(room_id).await?))
}
pub async fn get_invited_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
memberships: RoomMemberships,
stripped: bool,
) -> StoreResult<Vec<OwnedUserId>> {
let db = self.clone();
let key = self.encode_key(keys::INVITED_USER_ID, room_id);
let store_name = if stripped { keys::STRIPPED_USER_ID } else { keys::USER_ID };
let key = self.encode_key(store_name, room_id);
spawn_blocking(move || {
stream::iter(db.invited_user_ids.scan_prefix(key).map(move |u| {
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
.map_err(StoreError::backend)
}))
let tree = if stripped { &db.stripped_user_ids } else { &db.user_ids };
tree.scan_prefix(key)
.map(move |u| {
let member = db
.deserialize_value::<RoomMember>(&u.map_err(StoreError::backend)?.1)
.map_err(StoreError::backend)?;
Ok(memberships.matches(&member.membership).then_some(member.user_id))
})
.filter_map(|u| u.transpose())
.collect::<StoreResult<Vec<_>>>()
})
.await
.map_err(StoreError::backend)
}
pub async fn get_joined_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
let db = self.clone();
let key = self.encode_key(keys::JOINED_USER_ID, room_id);
spawn_blocking(move || {
stream::iter(db.joined_user_ids.scan_prefix(key).map(move |u| {
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
.map_err(StoreError::backend)
}))
})
.await
.map_err(StoreError::backend)
}
pub async fn get_stripped_invited_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
let db = self.clone();
let key = self.encode_key(keys::STRIPPED_INVITED_USER_ID, room_id);
spawn_blocking(move || {
stream::iter(db.stripped_invited_user_ids.scan_prefix(key).map(move |u| {
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
.map_err(StoreError::backend)
}))
})
.await
.map_err(StoreError::backend)
}
pub async fn get_stripped_joined_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
let db = self.clone();
let key = self.encode_key(keys::STRIPPED_JOINED_USER_ID, room_id);
spawn_blocking(move || {
stream::iter(db.stripped_joined_user_ids.scan_prefix(key).map(move |u| {
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
.map_err(StoreError::backend)
}))
})
.await
.map_err(StoreError::backend)
.map_err(StoreError::backend)?
}
pub async fn get_room_infos(&self) -> Result<impl Stream<Item = Result<RoomInfo>>> {
@@ -1210,38 +1081,18 @@ impl SledStateStore {
display_names_batch.remove(key?);
}
let mut joined_user_ids_batch = sled::Batch::default();
for key in
self.joined_user_ids.scan_prefix(self.encode_key(keys::JOINED_USER_ID, room_id)).keys()
{
joined_user_ids_batch.remove(key?);
let mut user_ids_batch = sled::Batch::default();
for key in self.user_ids.scan_prefix(self.encode_key(keys::USER_ID, room_id)).keys() {
user_ids_batch.remove(key?);
}
let mut stripped_joined_user_ids_batch = sled::Batch::default();
let mut stripped_user_ids_batch = sled::Batch::default();
for key in self
.stripped_joined_user_ids
.scan_prefix(self.encode_key(keys::STRIPPED_JOINED_USER_ID, room_id))
.stripped_user_ids
.scan_prefix(self.encode_key(keys::STRIPPED_USER_ID, room_id))
.keys()
{
stripped_joined_user_ids_batch.remove(key?);
}
let mut invited_user_ids_batch = sled::Batch::default();
for key in self
.invited_user_ids
.scan_prefix(self.encode_key(keys::INVITED_USER_ID, room_id))
.keys()
{
invited_user_ids_batch.remove(key?);
}
let mut stripped_invited_user_ids_batch = sled::Batch::default();
for key in self
.stripped_invited_user_ids
.scan_prefix(self.encode_key(keys::STRIPPED_INVITED_USER_ID, room_id))
.keys()
{
stripped_invited_user_ids_batch.remove(key?);
stripped_user_ids_batch.remove(key?);
}
let mut room_state_batch = sled::Batch::default();
@@ -1270,10 +1121,8 @@ impl SledStateStore {
let ret: Result<(), TransactionError<SledStoreError>> = (
&self.profiles,
&self.display_names,
&self.joined_user_ids,
&self.stripped_joined_user_ids,
&self.invited_user_ids,
&self.stripped_invited_user_ids,
&self.user_ids,
&self.stripped_user_ids,
&self.room_info,
&self.stripped_room_infos,
&self.room_state,
@@ -1284,10 +1133,8 @@ impl SledStateStore {
|(
profiles,
display_names,
joined,
stripped_joined,
invited,
stripped_invited,
user_ids,
stripped_user_ids,
rooms,
stripped_rooms,
state,
@@ -1299,10 +1146,8 @@ impl SledStateStore {
profiles.apply_batch(&profiles_batch)?;
display_names.apply_batch(&display_names_batch)?;
joined.apply_batch(&joined_user_ids_batch)?;
stripped_joined.apply_batch(&stripped_joined_user_ids_batch)?;
invited.apply_batch(&invited_user_ids_batch)?;
stripped_invited.apply_batch(&stripped_invited_user_ids_batch)?;
user_ids.apply_batch(&user_ids_batch)?;
stripped_user_ids.apply_batch(&stripped_user_ids_batch)?;
state.apply_batch(&room_state_batch)?;
stripped_state.apply_batch(&stripped_room_state_batch)?;
room_account_data.apply_batch(&room_account_data_batch)?;
@@ -1413,31 +1258,24 @@ impl StateStore for SledStateStore {
self.get_member_event(room_id, state_key).await.map_err(Into::into)
}
async fn get_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
let v: Vec<OwnedUserId> =
self.get_stripped_user_ids_stream(room_id).await?.try_collect().await?;
async fn get_user_ids(
&self,
room_id: &RoomId,
memberships: RoomMemberships,
) -> StoreResult<Vec<OwnedUserId>> {
let v = self.get_user_ids(room_id, memberships, true).await?;
if !v.is_empty() {
return Ok(v);
}
self.get_user_ids_stream(room_id).await?.try_collect().await
self.get_user_ids(room_id, memberships, false).await
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
let v: Vec<OwnedUserId> =
self.get_stripped_invited_user_ids(room_id).await?.try_collect().await?;
if !v.is_empty() {
return Ok(v);
}
self.get_invited_user_ids(room_id).await?.try_collect().await
StateStore::get_user_ids(self, room_id, RoomMemberships::INVITE).await
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
let v: Vec<OwnedUserId> =
self.get_stripped_joined_user_ids(room_id).await?.try_collect().await?;
if !v.is_empty() {
return Ok(v);
}
self.get_joined_user_ids(room_id).await?.try_collect().await
StateStore::get_user_ids(self, room_id, RoomMemberships::JOIN).await
}
async fn get_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {
@@ -1538,6 +1376,25 @@ impl StateStore for SledStateStore {
}
}
/// A room member.
#[derive(Debug, Serialize, Deserialize)]
struct RoomMember {
user_id: OwnedUserId,
membership: MembershipState,
}
impl From<&SyncStateEvent<RoomMemberEventContent>> for RoomMember {
fn from(event: &SyncStateEvent<RoomMemberEventContent>) -> Self {
Self { user_id: event.state_key().clone(), membership: event.membership().clone() }
}
}
impl From<&StrippedRoomMemberEvent> for RoomMember {
fn from(event: &StrippedRoomMemberEvent) -> Self {
Self { user_id: event.state_key.clone(), membership: event.content.membership.clone() }
}
}
#[cfg(test)]
mod tests {
use matrix_sdk_base::statestore_integration_tests;

View File

@@ -1,3 +1,9 @@
# unreleased
- `Common::members` and `Common::members_no_sync` take a `RoomMemberships` to be able to filter the
results by any membership state.
- `Common::active_members(_no_sync)` and `Common::joined_members(_no_sync)` are deprecated.
# 0.6.2
- Fix the access token being printed in tracing span fields.

View File

@@ -14,8 +14,12 @@
use std::sync::Arc;
use matrix_sdk_base::crypto::{
types::MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity, UserIdentity as InnerUserIdentity,
use matrix_sdk_base::{
crypto::{
types::MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity,
UserIdentity as InnerUserIdentity,
},
RoomMemberships,
};
use ruma::{
events::{
@@ -465,7 +469,7 @@ impl OtherUserIdentity {
let room = if let Some(room) = self.direct_message_room.read().await.as_ref() {
// Make sure that the user, to be verified, is still in the room
if !room
.active_members()
.members(RoomMemberships::ACTIVE)
.await?
.iter()
.any(|member| member.user_id() == self.inner.user_id())

View File

@@ -20,7 +20,7 @@ pub use async_trait::async_trait;
pub use bytes;
pub use matrix_sdk_base::{
deserialized_responses, DisplayName, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember,
RoomState, Session, StateChanges, StoreError,
RoomMemberships, RoomState, Session, StateChanges, StoreError,
};
pub use matrix_sdk_common::*;
pub use reqwest;

View File

@@ -3,7 +3,7 @@ use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc};
use matrix_sdk_base::{
deserialized_responses::{MembersResponse, TimelineEvent},
store::StateStoreExt,
StateChanges,
RoomMemberships, StateChanges,
};
#[cfg(feature = "e2e-encryption")]
use ruma::events::{
@@ -431,9 +431,10 @@ impl Common {
///
/// Use [active_members_no_sync()](#method.active_members_no_sync) if you
/// want a method that doesn't do any requests.
#[deprecated = "Use members with RoomMemberships::ACTIVE instead"]
pub async fn active_members(&self) -> Result<Vec<RoomMember>> {
self.ensure_members().await?;
self.active_members_no_sync().await
self.members_no_sync(RoomMemberships::ACTIVE).await
}
/// Get active members for this room, includes invited, joined members.
@@ -444,14 +445,9 @@ impl Common {
///
/// Use [active_members()](#method.active_members) if you want to ensure to
/// always get the full member list.
#[deprecated = "Use members_no_sync with RoomMemberships::ACTIVE instead"]
pub async fn active_members_no_sync(&self) -> Result<Vec<RoomMember>> {
Ok(self
.inner
.active_members()
.await?
.into_iter()
.map(|member| RoomMember::new(self.client.clone(), member))
.collect())
self.members_no_sync(RoomMemberships::ACTIVE).await
}
/// Get all the joined members of this room.
@@ -462,9 +458,10 @@ impl Common {
///
/// Use [joined_members_no_sync()](#method.joined_members_no_sync) if you
/// want a method that doesn't do any requests.
#[deprecated = "Use members with RoomMemberships::JOIN instead"]
pub async fn joined_members(&self) -> Result<Vec<RoomMember>> {
self.ensure_members().await?;
self.joined_members_no_sync().await
self.members_no_sync(RoomMemberships::JOIN).await
}
/// Get all the joined members of this room.
@@ -475,14 +472,9 @@ impl Common {
///
/// Use [joined_members()](#method.joined_members) if you want to ensure to
/// always get the full member list.
#[deprecated = "Use members_no_sync with RoomMemberships::JOIN instead"]
pub async fn joined_members_no_sync(&self) -> Result<Vec<RoomMember>> {
Ok(self
.inner
.joined_members()
.await?
.into_iter()
.map(|member| RoomMember::new(self.client.clone(), member))
.collect())
self.members_no_sync(RoomMemberships::JOIN).await
}
/// Get a specific member of this room.
@@ -524,8 +516,7 @@ impl Common {
.map(|member| RoomMember::new(self.client.clone(), member)))
}
/// Get all members for this room, includes invited, joined and left
/// members.
/// Get members for this room, with the given memberships.
///
/// *Note*: This method will fetch the members from the homeserver if the
/// member list isn't synchronized due to member lazy loading. Because of
@@ -533,13 +524,12 @@ impl Common {
///
/// Use [members_no_sync()](#method.members_no_sync) if you want a
/// method that doesn't do any requests.
pub async fn members(&self) -> Result<Vec<RoomMember>> {
pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
self.ensure_members().await?;
self.members_no_sync().await
self.members_no_sync(memberships).await
}
/// Get all members for this room, includes invited, joined and left
/// members.
/// Get members for this room, with the given memberships.
///
/// *Note*: This method will not fetch the members from the homeserver if
/// the member list isn't synchronized due to member lazy loading. Thus,
@@ -547,10 +537,10 @@ impl Common {
///
/// Use [members()](#method.members) if you want to ensure to always get
/// the full member list.
pub async fn members_no_sync(&self) -> Result<Vec<RoomMember>> {
pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
Ok(self
.inner
.members()
.members(memberships)
.await?
.into_iter()
.map(|member| RoomMember::new(self.client.clone(), member))
@@ -701,7 +691,8 @@ impl Common {
/// Returns true if all devices in the room are verified, otherwise false.
#[cfg(feature = "e2e-encryption")]
pub async fn contains_only_verified_devices(&self) -> Result<bool> {
let user_ids = self.client.store().get_user_ids(self.room_id()).await?;
let user_ids =
self.client.store().get_user_ids(self.room_id(), RoomMemberships::empty()).await?;
for user_id in user_ids {
let devices = self.client.encryption().get_user_devices(&user_id).await?;
@@ -800,7 +791,7 @@ impl Common {
let this_room_id = self.inner.room_id();
if is_direct {
let mut room_members = self.active_members().await?;
let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
room_members.retain(|member| member.user_id() != self.own_user_id());
for member in room_members {
@@ -866,7 +857,7 @@ impl Common {
// - Are blocked due to server ACLs
// - Are IP addresses
let members: Vec<_> = self
.joined_members_no_sync()
.members_no_sync(RoomMemberships::JOIN)
.await?
.into_iter()
.filter(|member| {

View File

@@ -4,6 +4,8 @@ use std::io::Cursor;
use std::sync::Arc;
use std::{borrow::Borrow, ops::Deref};
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_base::RoomMemberships;
use matrix_sdk_common::instant::{Duration, Instant};
use mime::{self, Mime};
use ruma::{
@@ -385,11 +387,12 @@ impl Joined {
let _guard = mutex.lock().await;
{
let joined = self.client.store().get_joined_user_ids(self.inner.room_id()).await?;
let invited =
self.client.store().get_invited_user_ids(self.inner.room_id()).await?;
let members = joined.iter().chain(&invited).map(Deref::deref);
self.client.claim_one_time_keys(members).await?;
let members = self
.client
.store()
.get_user_ids(self.inner.room_id(), RoomMemberships::ACTIVE)
.await?;
self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
};
let response = self.share_room_key().await;

View File

@@ -41,9 +41,10 @@ impl RoomMember {
///
/// ```no_run
/// # use futures::executor::block_on;
/// # use matrix_sdk::{
/// # media::MediaFormat, room::RoomMember, ruma::room_id, Client,
/// # };
/// use matrix_sdk::{
/// media::MediaFormat, room::RoomMember, ruma::room_id, Client,
/// RoomMemberships,
/// };
/// # use url::Url;
/// # let homeserver = Url::parse("http://example.com").unwrap();
/// # block_on(async {
@@ -52,7 +53,7 @@ impl RoomMember {
/// client.login_username(user, "password").send().await.unwrap();
/// let room_id = room_id!("!roomid:example.com");
/// let room = client.get_joined_room(&room_id).unwrap();
/// let members = room.members().await.unwrap();
/// let members = room.members(RoomMemberships::empty()).await.unwrap();
/// let member = members.first().unwrap();
/// if let Some(avatar) = member.avatar(MediaFormat::File).await.unwrap() {
/// std::fs::write("avatar.png", avatar);

View File

@@ -177,7 +177,10 @@ impl From<AnySyncTimelineEvent> for TimelineEventKind {
#[derive(Debug)]
pub(super) enum TimelineItemPosition {
Start,
End,
End {
/// Whether this event is coming from a local cache.
from_cache: bool,
},
#[cfg(feature = "e2e-encryption")]
Update(usize),
}
@@ -569,7 +572,8 @@ impl<'a> TimelineEventHandler<'a> {
let origin = match position {
TimelineItemPosition::Start => RemoteEventOrigin::Pagination,
// We only paginate backwards for now, so End only happens for syncs
TimelineItemPosition::End => RemoteEventOrigin::Sync,
TimelineItemPosition::End { from_cache: true } => RemoteEventOrigin::Cache,
TimelineItemPosition::End { from_cache: false } => RemoteEventOrigin::Sync,
#[cfg(feature = "e2e-encryption")]
TimelineItemPosition::Update(idx) => self.items[*idx]
.as_event()
@@ -670,7 +674,9 @@ impl<'a> TimelineEventHandler<'a> {
self.items.insert(offset + 1, Arc::new(item.into()));
}
Flow::Remote { position: TimelineItemPosition::End, txn_id, event_id, .. } => {
Flow::Remote {
position: TimelineItemPosition::End { .. }, txn_id, event_id, ..
} => {
let result = rfind_event_item(self.items, |it| {
txn_id.is_some() && it.transaction_id() == txn_id.as_deref()
|| it.event_id() == Some(event_id)

View File

@@ -68,6 +68,8 @@ impl RemoteEventTimelineItem {
/// Where we got an event from.
#[derive(Clone, Copy, Debug)]
pub(in crate::room::timeline) enum RemoteEventOrigin {
/// The event came from a cache.
Cache,
/// The event came from a sync response.
Sync,
/// The event came from pagination.

View File

@@ -150,7 +150,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
event.event,
event.encryption_info,
event.push_actions,
TimelineItemPosition::End,
TimelineItemPosition::End { from_cache: true },
state,
&self.room_data_provider,
self.track_read_receipts,
@@ -182,7 +182,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
raw,
encryption_info,
push_actions,
TimelineItemPosition::End,
TimelineItemPosition::End { from_cache: false },
&mut state,
&self.room_data_provider,
self.track_read_receipts,

View File

@@ -10,7 +10,9 @@ use ruma::{
self, AccountDataConfig, E2EEConfig, ExtensionsConfig, ReceiptsConfig, ToDeviceConfig,
TypingConfig,
},
assign, OwnedRoomId,
assign,
events::TimelineEventType,
OwnedRoomId,
};
use tokio::sync::{
mpsc::{channel, Receiver, Sender},
@@ -35,6 +37,7 @@ pub struct SlidingSyncBuilder {
homeserver: Option<Url>,
client: Option<Client>,
lists: BTreeMap<String, SlidingSyncList>,
bump_event_types: Vec<TimelineEventType>,
extensions: Option<ExtensionsConfig>,
subscriptions: BTreeMap<OwnedRoomId, v4::RoomSubscription>,
internal_channel: (Sender<SlidingSyncInternalMessage>, Receiver<SlidingSyncInternalMessage>),
@@ -47,6 +50,7 @@ impl SlidingSyncBuilder {
homeserver: None,
client: None,
lists: BTreeMap::new(),
bump_event_types: Vec::new(),
extensions: None,
subscriptions: BTreeMap::new(),
internal_channel: channel(8),
@@ -198,6 +202,17 @@ impl SlidingSyncBuilder {
self
}
/// Allowlist of event types which should be considered recent activity
/// when sorting `by_recency`. By omitting event types, clients can ensure
/// that uninteresting events (e.g. a profile rename) do not cause a
/// room to jump to the top of its list(s). Empty or
/// omitted `bump_event_types` have no effect: all events in a room will
/// be considered recent activity.
pub fn bump_event_types(mut self, bump_event_types: &[TimelineEventType]) -> Self {
self.bump_event_types = bump_event_types.to_vec();
self
}
/// Build the Sliding Sync.
///
/// If `self.storage_key` is `Some(_)`, load the cached data from cold
@@ -231,6 +246,7 @@ impl SlidingSyncBuilder {
lists,
rooms,
bump_event_types: self.bump_event_types,
extensions: Mutex::new(self.extensions),
reset_counter: Default::default(),

View File

@@ -562,9 +562,10 @@ impl SlidingSyncListInner {
self.set_ranges(&[(0, range_end)]);
// Finally, let's update the list' state.
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
*state = SlidingSyncState::PartiallyLoaded;
});
Observable::set_if_not_eq(
&mut self.state.write().unwrap(),
SlidingSyncState::PartiallyLoaded,
);
}
// Otherwise the current range has reached its maximum, we switched to `FullyLoaded`
// mode.
@@ -579,17 +580,19 @@ impl SlidingSyncListInner {
self.set_ranges(&[(0, range_maximum)]);
// Finally, let's update the list' state.
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
*state = SlidingSyncState::FullyLoaded;
});
Observable::set_if_not_eq(
&mut self.state.write().unwrap(),
SlidingSyncState::FullyLoaded,
);
}
}
SlidingSyncListRequestGeneratorKind::Selective => {
// Selective mode always loads everything.
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
*state = SlidingSyncState::FullyLoaded;
});
Observable::set_if_not_eq(
&mut self.state.write().unwrap(),
SlidingSyncState::FullyLoaded,
);
}
}

View File

@@ -49,7 +49,9 @@ use ruma::{
self, AccountDataConfig, E2EEConfig, ExtensionsConfig, ToDeviceConfig,
},
},
assign, OwnedRoomId, RoomId,
assign,
events::TimelineEventType,
OwnedRoomId, RoomId,
};
use serde::{Deserialize, Serialize};
use tokio::{
@@ -106,13 +108,17 @@ pub(super) struct SlidingSyncInner {
/// The rooms details
rooms: StdRwLock<BTreeMap<OwnedRoomId, SlidingSyncRoom>>,
/// The `bump_event_types` field. See
/// [`SlidingSyncBuilder::bump_event_types`] to learn more.
bump_event_types: Vec<TimelineEventType>,
subscriptions: StdRwLock<BTreeMap<OwnedRoomId, v4::RoomSubscription>>,
unsubscribe: StdRwLock<Vec<OwnedRoomId>>,
/// Number of times a Sliding Session session has been reset.
reset_counter: AtomicU8,
/// the intended state of the extensions being supplied to sliding /sync
/// The intended state of the extensions being supplied to sliding /sync
/// calls. May contain the latest next_batch for to_devices, etc.
extensions: Mutex<Option<ExtensionsConfig>>,
@@ -406,6 +412,7 @@ impl SlidingSync {
txn_id: Some(stream_id.to_owned()),
timeout: Some(timeout),
lists: requests_lists,
bump_event_types: self.inner.bump_event_types.clone(),
room_subscriptions,
unsubscribe_rooms,
extensions,

View File

@@ -1,7 +1,7 @@
use std::time::Duration;
use assert_matches::assert_matches;
use matrix_sdk::{config::SyncSettings, room::RoomMember, DisplayName};
use matrix_sdk::{config::SyncSettings, room::RoomMember, DisplayName, RoomMemberships};
use matrix_sdk_test::{
async_test, bulk_room_members, test_json, EventBuilder, JoinedRoomBuilder, StateTestEvent,
TimelineTestEvent,
@@ -40,7 +40,7 @@ async fn user_presence() {
let _response = client.sync_once(sync_settings).await.unwrap();
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
let members: Vec<RoomMember> = room.active_members().await.unwrap();
let members: Vec<RoomMember> = room.members(RoomMemberships::ACTIVE).await.unwrap();
assert_eq!(2, members.len());
// assert!(room.power_levels.is_some())

View File

@@ -9,7 +9,7 @@ use matrix_sdk::{
api::client::room::create_room::v3::Request as CreateRoomRequest,
events::room::member::{MembershipState, StrippedRoomMemberEvent},
},
Client, RoomState,
Client, RoomMemberships, RoomState,
};
use tokio::sync::Notify;
@@ -105,11 +105,11 @@ async fn test_repeated_join_leave() -> Result<()> {
// Now check the underlying state store that it also has the correct information
// (for when the client restarts).
let invited = karl.store().get_invited_user_ids(room_id).await?;
let invited = karl.store().get_user_ids(room_id, RoomMemberships::INVITE).await?;
assert_eq!(invited.len(), 1);
assert_eq!(invited[0], karl_id);
let joined = karl.store().get_joined_user_ids(room_id).await?;
let joined = karl.store().get_user_ids(room_id, RoomMemberships::JOIN).await?;
assert!(!joined.contains(&karl_id));
let event = karl

View File

@@ -18,6 +18,7 @@ pub enum TimelineTestEvent {
HistoryVisibility,
JoinRules,
Member,
MemberBan,
MemberInvite,
MemberNameChange,
MessageEdit,
@@ -47,6 +48,7 @@ impl TimelineTestEvent {
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
Self::Member => test_json::sync_events::MEMBER.to_owned(),
Self::MemberBan => test_json::sync_events::MEMBER_BAN.to_owned(),
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
Self::MessageEdit => test_json::sync_events::MESSAGE_EDIT.to_owned(),
@@ -81,6 +83,7 @@ pub enum StateTestEvent {
HistoryVisibility,
JoinRules,
Member,
MemberBan,
MemberInvite,
MemberNameChange,
PowerLevels,
@@ -103,6 +106,7 @@ impl StateTestEvent {
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
Self::Member => test_json::sync_events::MEMBER.to_owned(),
Self::MemberBan => test_json::sync_events::MEMBER_BAN.to_owned(),
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
Self::PowerLevels => test_json::sync_events::POWER_LEVELS.to_owned(),

View File

@@ -27,10 +27,10 @@ pub use sync::{
MORE_SYNC, MORE_SYNC_2, SYNC, VOIP_SYNC,
};
pub use sync_events::{
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
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,
TOPIC, TOPIC_REDACTION, TYPING,
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_BAN, MEMBER_INVITE, MEMBER_NAME_CHANGE,
MEMBER_STRIPPED, 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, TOPIC, TOPIC_REDACTION, TYPING,
};
/// An empty response.

View File

@@ -146,6 +146,21 @@ pub static MEMBER: Lazy<JsonValue> = Lazy::new(|| {
})
});
pub static MEMBER_BAN: Lazy<JsonValue> = Lazy::new(|| {
json!({
"content": {
"avatar_url": null,
"displayname": "example",
"membership": "ban"
},
"event_id": "$151800140517rfvjc:localhost",
"origin_server_ts": 151800140,
"sender": "@example:localhost",
"state_key": "@banned:localhost",
"type": "m.room.member",
})
});
pub static MEMBER_INVITE: Lazy<JsonValue> = Lazy::new(|| {
json!({
"content": {