2020-02-21 15:54:05 +00:00
|
|
|
|
// Copyright 2020 Damir Jelić
|
|
|
|
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
2020-09-12 01:19:22 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use std::{collections::BTreeMap, io::Write, path::PathBuf};
|
2021-03-23 14:30:40 +00:00
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use std::{
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
io::{Error as IoError, ErrorKind as IoErrorKind},
|
|
|
|
|
ops::Range,
|
|
|
|
|
};
|
2020-07-30 23:22:48 +00:00
|
|
|
|
use std::{
|
2020-12-01 14:08:53 +00:00
|
|
|
|
convert::TryInto,
|
2020-07-30 23:22:48 +00:00
|
|
|
|
fmt::{self, Debug},
|
2020-08-11 15:25:33 +00:00
|
|
|
|
future::Future,
|
2020-09-14 18:07:55 +00:00
|
|
|
|
io::Read,
|
2020-07-30 23:22:48 +00:00
|
|
|
|
path::Path,
|
|
|
|
|
result::Result as StdResult,
|
|
|
|
|
sync::Arc,
|
|
|
|
|
};
|
|
|
|
|
|
2020-08-12 13:12:51 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use dashmap::DashMap;
|
2020-08-11 15:25:33 +00:00
|
|
|
|
use futures_timer::Delay as sleep;
|
2020-09-15 15:16:16 +00:00
|
|
|
|
use http::HeaderValue;
|
2021-03-23 14:30:40 +00:00
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use http::Response;
|
2020-10-13 09:00:52 +00:00
|
|
|
|
use mime::{self, Mime};
|
2021-03-23 14:30:40 +00:00
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use rand::{thread_rng, Rng};
|
2020-09-15 15:16:16 +00:00
|
|
|
|
use reqwest::header::InvalidHeaderValue;
|
2021-03-23 14:30:40 +00:00
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use tokio::{net::TcpListener, sync::oneshot};
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use tokio_stream::wrappers::TcpListenerStream;
|
2020-08-11 15:25:33 +00:00
|
|
|
|
use url::Url;
|
2021-03-23 14:30:40 +00:00
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use warp::Filter;
|
2020-09-11 14:34:39 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use zeroize::Zeroizing;
|
2020-08-11 15:25:33 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use tracing::{debug, warn};
|
|
|
|
|
use tracing::{error, info, instrument};
|
|
|
|
|
|
2020-12-11 08:52:39 +00:00
|
|
|
|
use matrix_sdk_base::{
|
2021-04-05 17:49:55 +00:00
|
|
|
|
deserialized_responses::SyncResponse, events::AnyMessageEventContent, identifiers::MxcUri,
|
|
|
|
|
BaseClient, BaseClientConfig, Session, Store,
|
2020-12-11 08:52:39 +00:00
|
|
|
|
};
|
2020-08-11 15:25:33 +00:00
|
|
|
|
|
2020-08-10 10:39:00 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-09-11 14:34:39 +00:00
|
|
|
|
use matrix_sdk_base::crypto::{
|
|
|
|
|
decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError,
|
2021-03-08 23:12:59 +00:00
|
|
|
|
OutgoingRequests, RoomMessageRequest, ToDeviceRequest,
|
2020-09-11 14:34:39 +00:00
|
|
|
|
};
|
2020-08-12 09:39:47 +00:00
|
|
|
|
|
2020-10-06 13:04:43 +00:00
|
|
|
|
/// Enum controlling if a loop running callbacks should continue or abort.
|
|
|
|
|
///
|
|
|
|
|
/// This is mainly used in the [`sync_with_callback`] method, the return value
|
|
|
|
|
/// of the provided callback controls if the sync loop should be exited.
|
|
|
|
|
///
|
|
|
|
|
/// [`sync_with_callback`]: #method.sync_with_callback
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub enum LoopCtrl {
|
|
|
|
|
/// Continue running the loop.
|
|
|
|
|
Continue,
|
|
|
|
|
/// Break out of the loop.
|
|
|
|
|
Break,
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-30 23:22:48 +00:00
|
|
|
|
use matrix_sdk_common::{
|
2020-08-12 14:52:50 +00:00
|
|
|
|
api::r0::{
|
|
|
|
|
account::register,
|
2020-10-16 15:27:00 +00:00
|
|
|
|
device::{delete_devices, get_devices},
|
2020-08-12 14:52:50 +00:00
|
|
|
|
directory::{get_public_rooms, get_public_rooms_filtered},
|
2020-11-22 20:25:31 +00:00
|
|
|
|
filter::{create_filter::Request as FilterUploadRequest, FilterDefinition},
|
2021-03-19 16:26:01 +00:00
|
|
|
|
media::{create_content, get_content, get_content_thumbnail},
|
2021-03-08 23:12:59 +00:00
|
|
|
|
membership::{join_room_by_id, join_room_by_id_or_alias},
|
|
|
|
|
message::send_message_event,
|
2020-12-07 15:34:32 +00:00
|
|
|
|
profile::{get_avatar_url, get_display_name, set_avatar_url, set_display_name},
|
2020-08-12 14:52:50 +00:00
|
|
|
|
room::create_room,
|
2021-03-23 13:47:15 +00:00
|
|
|
|
session::{get_login_types, login, sso_login},
|
2020-08-12 14:52:50 +00:00
|
|
|
|
sync::sync_events,
|
2020-10-16 15:27:00 +00:00
|
|
|
|
uiaa::AuthData,
|
2020-08-12 14:52:50 +00:00
|
|
|
|
},
|
2020-08-15 01:09:13 +00:00
|
|
|
|
assign,
|
2020-10-16 17:45:38 +00:00
|
|
|
|
identifiers::{DeviceIdBox, EventId, RoomId, RoomIdOrAliasId, ServerName, UserId},
|
2020-07-30 23:22:48 +00:00
|
|
|
|
instant::{Duration, Instant},
|
2021-03-17 10:53:18 +00:00
|
|
|
|
locks::RwLock,
|
2020-07-30 23:22:48 +00:00
|
|
|
|
presence::PresenceState,
|
|
|
|
|
uuid::Uuid,
|
2021-01-04 14:29:49 +00:00
|
|
|
|
FromHttpResponseError, UInt,
|
2020-07-30 23:22:48 +00:00
|
|
|
|
};
|
2020-04-09 14:19:32 +00:00
|
|
|
|
|
2020-08-12 14:52:50 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2021-03-12 16:02:25 +00:00
|
|
|
|
use matrix_sdk_common::api::r0::{
|
|
|
|
|
keys::{get_keys, upload_keys, upload_signing_keys::Request as UploadSigningKeysRequest},
|
|
|
|
|
to_device::send_event_to_device::{
|
|
|
|
|
Request as RumaToDeviceRequest, Response as ToDeviceResponse,
|
2020-08-12 14:52:50 +00:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-12 16:02:25 +00:00
|
|
|
|
use matrix_sdk_common::locks::Mutex;
|
|
|
|
|
|
2020-08-04 09:23:24 +00:00
|
|
|
|
use crate::{
|
2021-01-31 17:09:03 +00:00
|
|
|
|
error::HttpError,
|
2020-08-26 12:37:48 +00:00
|
|
|
|
http_client::{client_with_config, HttpClient, HttpSend},
|
2021-03-05 13:55:06 +00:00
|
|
|
|
room, Error, OutgoingRequest, Result,
|
2020-08-04 09:23:24 +00:00
|
|
|
|
};
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2020-04-01 13:37:00 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-17 13:34:05 +00:00
|
|
|
|
use crate::{
|
|
|
|
|
device::{Device, UserDevices},
|
2021-03-17 10:53:18 +00:00
|
|
|
|
event_handler::Handler,
|
2020-08-17 13:34:05 +00:00
|
|
|
|
identifiers::DeviceId,
|
|
|
|
|
sas::Sas,
|
2021-01-19 11:59:31 +00:00
|
|
|
|
verification_request::VerificationRequest,
|
2021-03-17 10:53:18 +00:00
|
|
|
|
EventHandler,
|
2020-08-17 13:34:05 +00:00
|
|
|
|
};
|
2020-04-01 13:37:00 +00:00
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
2020-03-24 15:18:56 +00:00
|
|
|
|
const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
/// A conservative upload speed of 1Mbps
|
|
|
|
|
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
|
2021-02-01 20:56:15 +00:00
|
|
|
|
/// 5 min minimal upload request timeout, used to clamp the request timeout.
|
|
|
|
|
const MIN_UPLOAD_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 5);
|
2021-03-23 14:30:40 +00:00
|
|
|
|
/// The range of ports the SSO server will try to bind to randomly
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
const SSO_SERVER_BIND_RANGE: Range<u16> = 20000..30000;
|
|
|
|
|
/// The number of timesthe SSO server will try to bind to a random port
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
const SSO_SERVER_BIND_TRIES: u8 = 10;
|
2020-02-28 09:33:17 +00:00
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// An async/await enabled Matrix client.
|
2020-04-15 11:52:29 +00:00
|
|
|
|
///
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// All of the state is held in an `Arc` so the `Client` can be cloned freely.
|
2020-05-06 13:36:55 +00:00
|
|
|
|
#[derive(Clone)]
|
2020-05-08 12:02:49 +00:00
|
|
|
|
pub struct Client {
|
2019-10-23 20:47:00 +00:00
|
|
|
|
/// The URL of the homeserver to connect to.
|
2020-07-29 08:56:18 +00:00
|
|
|
|
homeserver: Arc<Url>,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
/// The underlying HTTP client.
|
2020-07-29 08:56:18 +00:00
|
|
|
|
http_client: HttpClient,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
/// User session data.
|
2020-05-06 13:36:55 +00:00
|
|
|
|
pub(crate) base_client: BaseClient,
|
2020-08-12 13:12:51 +00:00
|
|
|
|
/// Locks making sure we only have one group session sharing request in
|
|
|
|
|
/// flight per room.
|
|
|
|
|
#[cfg(feature = "encryption")]
|
2021-03-16 15:37:50 +00:00
|
|
|
|
pub(crate) group_session_locks: Arc<DashMap<RoomId, Arc<Mutex<()>>>>,
|
2020-10-07 12:07:47 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
/// Lock making sure we're only doing one key claim request at a time.
|
|
|
|
|
key_claim_lock: Arc<Mutex<()>>,
|
2021-03-16 15:37:50 +00:00
|
|
|
|
pub(crate) members_request_locks: Arc<DashMap<RoomId, Arc<Mutex<()>>>>,
|
|
|
|
|
pub(crate) typing_notice_times: Arc<DashMap<RoomId, Instant>>,
|
2021-03-17 10:53:18 +00:00
|
|
|
|
/// Any implementor of EventHandler will act as the callbacks for various
|
|
|
|
|
/// events.
|
|
|
|
|
event_handler: Arc<RwLock<Option<Handler>>>,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 13:49:04 +00:00
|
|
|
|
#[cfg(not(tarpaulin_include))]
|
2020-06-02 18:57:56 +00:00
|
|
|
|
impl Debug for Client {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
|
2020-05-08 12:02:49 +00:00
|
|
|
|
write!(fmt, "Client {{ homeserver: {} }}", self.homeserver)
|
2020-03-19 12:55:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// Configuration for the creation of the `Client`.
|
2020-02-21 13:29:25 +00:00
|
|
|
|
///
|
2020-04-23 23:37:27 +00:00
|
|
|
|
/// When setting the `StateStore` it is up to the user to open/connect
|
|
|
|
|
/// the storage backend before client creation.
|
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// # use matrix_sdk::ClientConfig;
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// // To pass all the request through mitmproxy set the proxy and disable SSL
|
|
|
|
|
/// // verification
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// let client_config = ClientConfig::new()
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// .proxy("http://localhost:8080")
|
|
|
|
|
/// .unwrap()
|
|
|
|
|
/// .disable_ssl_verification();
|
|
|
|
|
/// ```
|
2020-05-22 13:23:58 +00:00
|
|
|
|
#[derive(Default)]
|
2020-05-08 12:02:49 +00:00
|
|
|
|
pub struct ClientConfig {
|
2020-05-08 14:12:21 +00:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2020-08-11 12:07:45 +00:00
|
|
|
|
pub(crate) proxy: Option<reqwest::Proxy>,
|
|
|
|
|
pub(crate) user_agent: Option<HeaderValue>,
|
|
|
|
|
pub(crate) disable_ssl_verification: bool,
|
|
|
|
|
pub(crate) base_config: BaseClientConfig,
|
2021-04-01 17:25:31 +00:00
|
|
|
|
pub(crate) request_config: RequestConfig,
|
2020-08-11 12:07:45 +00:00
|
|
|
|
pub(crate) client: Option<Arc<dyn HttpSend>>,
|
2020-04-22 21:39:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 13:49:04 +00:00
|
|
|
|
#[cfg(not(tarpaulin_include))]
|
2020-06-02 18:57:56 +00:00
|
|
|
|
impl Debug for ClientConfig {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-05-08 14:12:21 +00:00
|
|
|
|
let mut res = fmt.debug_struct("ClientConfig");
|
|
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
let res = res.field("proxy", &self.proxy);
|
|
|
|
|
|
|
|
|
|
res.field("user_agent", &self.user_agent)
|
2020-04-22 21:39:57 +00:00
|
|
|
|
.field("disable_ssl_verification", &self.disable_ssl_verification)
|
2021-04-01 17:25:31 +00:00
|
|
|
|
.field("request_config", &self.request_config)
|
2020-04-22 21:39:57 +00:00
|
|
|
|
.finish()
|
|
|
|
|
}
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-08 12:02:49 +00:00
|
|
|
|
impl ClientConfig {
|
|
|
|
|
/// Create a new default `ClientConfig`.
|
2019-10-23 20:47:00 +00:00
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Default::default()
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Set the proxy through which all the HTTP requests should go.
|
|
|
|
|
///
|
|
|
|
|
/// Note, only HTTP proxies are supported.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `proxy` - The HTTP URL of the proxy.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// use matrix_sdk::ClientConfig;
|
2020-02-21 13:29:25 +00:00
|
|
|
|
///
|
2020-05-08 12:02:49 +00:00
|
|
|
|
/// let client_config = ClientConfig::new()
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// .proxy("http://localhost:8080")
|
|
|
|
|
/// .unwrap();
|
|
|
|
|
/// ```
|
2020-05-08 14:12:21 +00:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2020-03-18 13:15:56 +00:00
|
|
|
|
pub fn proxy(mut self, proxy: &str) -> Result<Self> {
|
2019-10-23 20:47:00 +00:00
|
|
|
|
self.proxy = Some(reqwest::Proxy::all(proxy)?);
|
|
|
|
|
Ok(self)
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Disable SSL verification for the HTTP requests.
|
2019-10-23 20:47:00 +00:00
|
|
|
|
pub fn disable_ssl_verification(mut self) -> Self {
|
|
|
|
|
self.disable_ssl_verification = true;
|
|
|
|
|
self
|
|
|
|
|
}
|
2019-10-24 20:34:58 +00:00
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Set a custom HTTP user agent for the client.
|
2020-03-18 13:15:56 +00:00
|
|
|
|
pub fn user_agent(mut self, user_agent: &str) -> StdResult<Self, InvalidHeaderValue> {
|
2019-10-24 20:34:58 +00:00
|
|
|
|
self.user_agent = Some(HeaderValue::from_str(user_agent)?);
|
|
|
|
|
Ok(self)
|
|
|
|
|
}
|
2020-04-22 21:39:57 +00:00
|
|
|
|
|
2020-10-24 18:01:39 +00:00
|
|
|
|
///// Set a custom implementation of a `StateStore`.
|
|
|
|
|
/////
|
|
|
|
|
///// The state store should be opened before being set.
|
|
|
|
|
//pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
|
|
|
|
// self.base_config = self.base_config.state_store(store);
|
|
|
|
|
// self
|
|
|
|
|
//}
|
2020-05-25 12:21:04 +00:00
|
|
|
|
|
|
|
|
|
/// Set the path for storage.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `path` - The path where the stores should save data in. It is the
|
|
|
|
|
/// callers responsibility to make sure that the path exists.
|
|
|
|
|
///
|
|
|
|
|
/// In the default configuration the client will open default
|
|
|
|
|
/// implementations for the crypto store and the state store. It will use
|
|
|
|
|
/// the given path to open the stores. If no path is provided no store will
|
|
|
|
|
/// be opened
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub fn store_path(mut self, path: impl AsRef<Path>) -> Self {
|
2020-05-25 12:21:04 +00:00
|
|
|
|
self.base_config = self.base_config.store_path(path);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the passphrase to encrypt the crypto store.
|
|
|
|
|
///
|
|
|
|
|
/// # Argument
|
|
|
|
|
///
|
|
|
|
|
/// * `passphrase` - The passphrase that will be used to encrypt the data in
|
|
|
|
|
/// the cryptostore.
|
|
|
|
|
///
|
|
|
|
|
/// This is only used if no custom cryptostore is set.
|
|
|
|
|
pub fn passphrase(mut self, passphrase: String) -> Self {
|
|
|
|
|
self.base_config = self.base_config.passphrase(passphrase);
|
2020-04-22 21:39:57 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
2020-07-16 21:16:30 +00:00
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
/// Set the default timeout, fail and retry behavior for all HTTP requests.
|
|
|
|
|
pub fn request_config(mut self, request_config: RequestConfig) -> Self {
|
|
|
|
|
self.request_config = request_config;
|
2020-07-16 21:40:52 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
2020-08-11 12:07:45 +00:00
|
|
|
|
|
|
|
|
|
/// Specify a client to handle sending requests and receiving responses.
|
|
|
|
|
///
|
|
|
|
|
/// Any type that implements the `HttpSend` trait can be used to send/receive
|
|
|
|
|
/// `http` types.
|
|
|
|
|
pub fn client(mut self, client: Arc<dyn HttpSend>) -> Self {
|
|
|
|
|
self.client = Some(client);
|
|
|
|
|
self
|
|
|
|
|
}
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
#[derive(Debug, Clone)]
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Settings for a sync call.
|
2020-08-15 01:09:13 +00:00
|
|
|
|
pub struct SyncSettings<'a> {
|
|
|
|
|
pub(crate) filter: Option<sync_events::Filter<'a>>,
|
2020-03-24 15:18:56 +00:00
|
|
|
|
pub(crate) timeout: Option<Duration>,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
pub(crate) token: Option<String>,
|
2020-04-03 12:09:56 +00:00
|
|
|
|
pub(crate) full_state: bool,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
impl<'a> Default for SyncSettings<'a> {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
filter: Default::default(),
|
|
|
|
|
timeout: Some(DEFAULT_SYNC_TIMEOUT),
|
|
|
|
|
token: Default::default(),
|
|
|
|
|
full_state: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-15 01:09:13 +00:00
|
|
|
|
impl<'a> SyncSettings<'a> {
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Create new default sync settings.
|
2019-10-23 20:47:00 +00:00
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Default::default()
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Set the sync token.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `token` - The sync token that should be used for the sync call.
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub fn token(mut self, token: impl Into<String>) -> Self {
|
2019-10-23 20:47:00 +00:00
|
|
|
|
self.token = Some(token.into());
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Set the maximum time the server can wait, in milliseconds, before
|
|
|
|
|
/// responding to the sync request.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `timeout` - The time the server is allowed to wait.
|
2020-03-24 15:18:56 +00:00
|
|
|
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
|
|
|
|
self.timeout = Some(timeout);
|
|
|
|
|
self
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-16 13:13:35 +00:00
|
|
|
|
/// Set the sync filter.
|
|
|
|
|
/// It can be either the filter ID, or the definition for the filter.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `filter` - The filter configuration that should be used for the sync call.
|
2020-08-15 01:09:13 +00:00
|
|
|
|
pub fn filter(mut self, filter: sync_events::Filter<'a>) -> Self {
|
2020-07-16 13:13:35 +00:00
|
|
|
|
self.filter = Some(filter);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Should the server return the full state from the start of the timeline.
|
|
|
|
|
///
|
|
|
|
|
/// This does nothing if no sync token is set.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
/// * `full_state` - A boolean deciding if the server should return the full
|
|
|
|
|
/// state or not.
|
2019-10-23 20:47:00 +00:00
|
|
|
|
pub fn full_state(mut self, full_state: bool) -> Self {
|
2020-04-03 12:09:56 +00:00
|
|
|
|
self.full_state = full_state;
|
2019-10-23 20:47:00 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
/// Configuration for requests the `Client` makes.
|
|
|
|
|
///
|
|
|
|
|
/// This sets how often and for how long a request should be repeated. As well as how long a
|
|
|
|
|
/// successful request is allowed to take.
|
|
|
|
|
///
|
|
|
|
|
/// By default requests are retried indefinitely and use no timeout.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// # use matrix_sdk::RequestConfig;
|
|
|
|
|
/// # use std::time::Duration;
|
|
|
|
|
/// // This sets makes requests fail after a single send request and sets the timeout to 30s
|
|
|
|
|
/// let request_config = RequestConfig::new()
|
|
|
|
|
/// .disable_retry()
|
|
|
|
|
/// .timeout(Duration::from_secs(30));
|
|
|
|
|
/// ```
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
pub struct RequestConfig {
|
|
|
|
|
pub(crate) timeout: Duration,
|
|
|
|
|
pub(crate) retry_limit: Option<u64>,
|
|
|
|
|
pub(crate) retry_timeout: Option<Duration>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(tarpaulin_include))]
|
|
|
|
|
impl Debug for RequestConfig {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
let mut res = fmt.debug_struct("RequestConfig");
|
|
|
|
|
|
|
|
|
|
res.field("timeout", &self.timeout)
|
|
|
|
|
.field("retry_limit", &self.retry_limit)
|
|
|
|
|
.field("retry_timeout", &self.retry_timeout)
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for RequestConfig {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
|
|
|
retry_limit: Default::default(),
|
|
|
|
|
retry_timeout: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RequestConfig {
|
|
|
|
|
/// Create a new default `RequestConfig`.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Default::default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is a convince method to disable the retries of a request. Setting the `retry_limit` to `0`
|
|
|
|
|
/// has the same effect.
|
|
|
|
|
pub fn disable_retry(mut self) -> Self {
|
|
|
|
|
self.retry_limit = Some(0);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The number of times a request should be retried. The default is no limit
|
|
|
|
|
pub fn retry_limit(mut self, retry_limit: u64) -> Self {
|
|
|
|
|
self.retry_limit = Some(retry_limit);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the timeout duration for all HTTP requests.
|
|
|
|
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
|
|
|
|
self.timeout = timeout;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set a timeout for how long a request should be retried. The default is no timeout, meaning requests are retried forever.
|
|
|
|
|
pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self {
|
|
|
|
|
self.retry_timeout = Some(retry_timeout);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-08 12:02:49 +00:00
|
|
|
|
impl Client {
|
2019-10-23 20:47:00 +00:00
|
|
|
|
/// Creates a new client for making HTTP requests to the given homeserver.
|
2020-02-21 13:29:25 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `homeserver_url` - The homeserver that the client should connect to.
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub fn new(homeserver_url: impl TryInto<Url>) -> Result<Self> {
|
2020-05-08 12:02:49 +00:00
|
|
|
|
let config = ClientConfig::new();
|
2020-05-22 13:23:58 +00:00
|
|
|
|
Client::new_with_config(homeserver_url, config)
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Create a new client with the given configuration.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `homeserver_url` - The homeserver that the client should connect to.
|
2020-05-22 13:23:58 +00:00
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// * `config` - Configuration for the client.
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub fn new_with_config(
|
|
|
|
|
homeserver_url: impl TryInto<Url>,
|
2020-05-08 12:02:49 +00:00
|
|
|
|
config: ClientConfig,
|
2020-03-18 13:15:56 +00:00
|
|
|
|
) -> Result<Self> {
|
2020-07-31 00:05:49 +00:00
|
|
|
|
let homeserver = if let Ok(u) = homeserver_url.try_into() {
|
2020-08-11 12:07:45 +00:00
|
|
|
|
Arc::new(u)
|
2020-07-31 00:05:49 +00:00
|
|
|
|
} else {
|
|
|
|
|
panic!("Error parsing homeserver url")
|
2019-11-10 10:44:03 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-08-17 13:29:07 +00:00
|
|
|
|
let client = if let Some(client) = config.client {
|
|
|
|
|
client
|
2020-08-11 12:07:45 +00:00
|
|
|
|
} else {
|
2020-08-26 12:37:48 +00:00
|
|
|
|
Arc::new(client_with_config(&config)?)
|
2020-07-29 08:56:18 +00:00
|
|
|
|
};
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2020-05-25 12:21:04 +00:00
|
|
|
|
let base_client = BaseClient::new_with_config(config.base_config)?;
|
2020-08-17 13:29:07 +00:00
|
|
|
|
let session = base_client.session().clone();
|
|
|
|
|
|
|
|
|
|
let http_client = HttpClient {
|
|
|
|
|
homeserver: homeserver.clone(),
|
|
|
|
|
inner: client,
|
|
|
|
|
session,
|
2021-04-01 17:25:31 +00:00
|
|
|
|
request_config: config.request_config,
|
2020-08-17 13:29:07 +00:00
|
|
|
|
};
|
2020-04-22 21:39:57 +00:00
|
|
|
|
|
2019-10-23 20:47:00 +00:00
|
|
|
|
Ok(Self {
|
|
|
|
|
homeserver,
|
|
|
|
|
http_client,
|
2020-05-06 13:36:55 +00:00
|
|
|
|
base_client,
|
2020-08-12 14:19:41 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2021-03-16 15:37:50 +00:00
|
|
|
|
group_session_locks: Arc::new(DashMap::new()),
|
2020-10-07 12:07:47 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
key_claim_lock: Arc::new(Mutex::new(())),
|
2021-03-16 15:37:50 +00:00
|
|
|
|
members_request_locks: Arc::new(DashMap::new()),
|
|
|
|
|
typing_notice_times: Arc::new(DashMap::new()),
|
2021-03-17 10:53:18 +00:00
|
|
|
|
event_handler: Arc::new(RwLock::new(None)),
|
2019-10-23 20:47:00 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-11 21:24:32 +00:00
|
|
|
|
/// Is the client logged in.
|
2020-03-11 10:42:59 +00:00
|
|
|
|
pub async fn logged_in(&self) -> bool {
|
2020-05-06 13:36:55 +00:00
|
|
|
|
self.base_client.logged_in().await
|
2019-11-17 18:55:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-11 21:24:32 +00:00
|
|
|
|
/// The Homeserver of the client.
|
|
|
|
|
pub fn homeserver(&self) -> &Url {
|
|
|
|
|
&self.homeserver
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-05 14:47:38 +00:00
|
|
|
|
/// Get the user id of the current owner of the client.
|
|
|
|
|
pub async fn user_id(&self) -> Option<UserId> {
|
|
|
|
|
let session = self.base_client.session().read().await;
|
|
|
|
|
session.as_ref().cloned().map(|s| s.user_id)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-23 14:59:53 +00:00
|
|
|
|
/// Get the device id that identifies the current session.
|
|
|
|
|
pub async fn device_id(&self) -> Option<DeviceIdBox> {
|
|
|
|
|
let session = self.base_client.session().read().await;
|
|
|
|
|
session.as_ref().map(|s| s.device_id.clone())
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 10:17:26 +00:00
|
|
|
|
/// Fetches the display name of the owner of the client.
|
2020-12-07 11:59:10 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let user = "example";
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// client.login(user, "password", None, None).await.unwrap();
|
|
|
|
|
///
|
2020-12-07 12:14:23 +00:00
|
|
|
|
/// if let Some(name) = client.display_name().await.unwrap() {
|
2020-12-07 11:59:10 +00:00
|
|
|
|
/// println!("Logged in as user '{}' with display name '{}'", user, name);
|
|
|
|
|
/// }
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn display_name(&self) -> Result<Option<String>> {
|
2020-12-07 10:17:26 +00:00
|
|
|
|
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let request = get_display_name::Request::new(&user_id);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-12-07 10:17:26 +00:00
|
|
|
|
Ok(response.displayname)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sets the display name of the owner of the client.
|
2020-12-07 11:59:10 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let user = "example";
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// client.login(user, "password", None, None).await.unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// client.set_display_name(Some("Alice")).await.expect("Failed setting display name");
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
|
2020-12-07 10:17:26 +00:00
|
|
|
|
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let request = set_display_name::Request::new(&user_id, name);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await?;
|
2020-12-07 10:17:26 +00:00
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 15:34:32 +00:00
|
|
|
|
/// Gets the mxc avatar url of the owner of the client, if set.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let user = "example";
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// client.login(user, "password", None, None).await.unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// if let Some(url) = client.avatar_url().await.unwrap() {
|
|
|
|
|
/// println!("Your avatar's mxc url is {}", url);
|
|
|
|
|
/// }
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
2021-04-05 17:49:55 +00:00
|
|
|
|
pub async fn avatar_url(&self) -> Result<Option<MxcUri>> {
|
2020-12-07 15:34:32 +00:00
|
|
|
|
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let request = get_avatar_url::Request::new(&user_id);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-12-07 15:34:32 +00:00
|
|
|
|
Ok(response.avatar_url)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 16:26:01 +00:00
|
|
|
|
/// Gets the avatar of the owner of the client, if set.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the avatar. No guarantee on the size of the image is given.
|
|
|
|
|
/// If no size is given the full-sized avatar will be returned.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `width` - The desired width of the avatar.
|
|
|
|
|
///
|
|
|
|
|
/// * `height` - The desired height of the avatar.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::identifiers::room_id;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let user = "example";
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// client.login(user, "password", None, None).await.unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// if let Some(avatar) = client.avatar(Some(96), Some(96)).await.unwrap() {
|
|
|
|
|
/// std::fs::write("avatar.png", avatar);
|
|
|
|
|
/// }
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn avatar(&self, width: Option<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
|
|
|
|
|
// TODO: try to offer the avatar from cache, requires avatar cache
|
2021-04-05 17:49:55 +00:00
|
|
|
|
if let Some(url) = self.avatar_url().await? {
|
2021-03-19 16:26:01 +00:00
|
|
|
|
if let (Some(width), Some(height)) = (width, height) {
|
2021-04-05 17:49:55 +00:00
|
|
|
|
let request =
|
2021-04-11 10:04:53 +00:00
|
|
|
|
get_content_thumbnail::Request::from_url(&url, width.into(), height.into())?;
|
2021-03-19 16:26:01 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
|
|
|
|
Ok(Some(response.file))
|
|
|
|
|
} else {
|
2021-04-11 10:04:53 +00:00
|
|
|
|
let request = get_content::Request::from_url(&url)?;
|
2021-03-19 16:26:01 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
|
|
|
|
Ok(Some(response.file))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 08:52:39 +00:00
|
|
|
|
/// Get a reference to the store.
|
|
|
|
|
pub fn store(&self) -> &Store {
|
|
|
|
|
self.base_client.store()
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 15:34:32 +00:00
|
|
|
|
/// Sets the mxc avatar url of the client's owner. The avatar gets unset if `url` is `None`.
|
2021-04-05 17:49:55 +00:00
|
|
|
|
pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
|
2020-12-07 15:34:32 +00:00
|
|
|
|
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let request = set_avatar_url::Request::new(&user_id, url);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await?;
|
2020-12-07 15:34:32 +00:00
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Upload and set the owning client's avatar.
|
|
|
|
|
///
|
|
|
|
|
/// The will upload the data produced by the reader to the homeserver's content repository, and
|
|
|
|
|
/// set the user's avatar to the mxc url for the uploaded file.
|
|
|
|
|
///
|
|
|
|
|
/// This is a convenience method for calling [`upload()`](#method.upload), followed by
|
|
|
|
|
/// [`set_avatar_url()`](#method.set_avatar_url).
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::{path::Path, fs::File, io::Read};
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://locahost:8080").unwrap();
|
|
|
|
|
/// # let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let path = Path::new("/home/example/selfie.jpg");
|
|
|
|
|
/// let mut image = File::open(&path).unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// client.upload_avatar(&mime::IMAGE_JPEG, &mut image).await.expect("Can't set avatar");
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn upload_avatar<R: Read>(&self, content_type: &Mime, reader: &mut R) -> Result<()> {
|
|
|
|
|
let upload_response = self.upload(content_type, reader).await?;
|
|
|
|
|
self.set_avatar_url(Some(&upload_response.content_uri))
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-13 10:01:31 +00:00
|
|
|
|
/// Add `EventHandler` to `Client`.
|
2020-12-19 19:20:39 +00:00
|
|
|
|
///
|
2021-02-13 10:01:31 +00:00
|
|
|
|
/// The methods of `EventHandler` are called when the respective `RoomEvents` occur.
|
|
|
|
|
pub async fn set_event_handler(&self, handler: Box<dyn EventHandler>) {
|
2021-03-17 10:53:18 +00:00
|
|
|
|
let handler = Handler {
|
|
|
|
|
inner: handler,
|
|
|
|
|
client: self.clone(),
|
|
|
|
|
};
|
|
|
|
|
*self.event_handler.write().await = Some(handler);
|
2020-12-19 19:20:39 +00:00
|
|
|
|
}
|
2020-03-31 23:34:11 +00:00
|
|
|
|
|
2021-03-02 13:58:30 +00:00
|
|
|
|
/// Get all the rooms the client knows about.
|
|
|
|
|
///
|
|
|
|
|
/// This will return the list of joined, invited, and left rooms.
|
2021-03-16 17:22:09 +00:00
|
|
|
|
pub fn rooms(&self) -> Vec<room::Room> {
|
2021-03-05 13:55:06 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_rooms()
|
|
|
|
|
.into_iter()
|
2021-03-16 17:22:09 +00:00
|
|
|
|
.map(|room| room::Common::new(self.clone(), room).into())
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.collect()
|
2021-03-02 13:58:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-01 13:57:39 +00:00
|
|
|
|
/// Returns the joined rooms this client knows about.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn joined_rooms(&self) -> Vec<room::Joined> {
|
2021-01-01 13:57:39 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_rooms()
|
|
|
|
|
.into_iter()
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.filter_map(|room| room::Joined::new(self.clone(), room))
|
2021-01-01 13:57:39 +00:00
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the invited rooms this client knows about.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn invited_rooms(&self) -> Vec<room::Invited> {
|
2021-01-01 13:57:39 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_rooms()
|
|
|
|
|
.into_iter()
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.filter_map(|room| room::Invited::new(self.clone(), room))
|
2021-01-01 13:57:39 +00:00
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the left rooms this client knows about.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn left_rooms(&self) -> Vec<room::Left> {
|
2021-01-01 13:57:39 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_rooms()
|
|
|
|
|
.into_iter()
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.filter_map(|room| room::Left::new(self.clone(), room))
|
2021-01-01 13:57:39 +00:00
|
|
|
|
.collect()
|
|
|
|
|
}
|
2020-05-07 00:35:15 +00:00
|
|
|
|
|
2021-03-08 22:00:19 +00:00
|
|
|
|
/// Get a room with the given room id.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// `room_id` - The unique id of the room that should be fetched.
|
2021-03-16 17:22:09 +00:00
|
|
|
|
pub fn get_room(&self, room_id: &RoomId) -> Option<room::Room> {
|
2021-03-08 22:00:19 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_room(room_id)
|
2021-03-16 17:22:09 +00:00
|
|
|
|
.map(|room| room::Common::new(self.clone(), room).into())
|
2021-03-08 22:00:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 00:35:15 +00:00
|
|
|
|
/// Get a joined room with the given room id.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// `room_id` - The unique id of the room that should be fetched.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<room::Joined> {
|
2021-03-05 11:00:43 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_room(room_id)
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.and_then(|room| room::Joined::new(self.clone(), room))
|
2020-10-24 18:01:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-22 09:47:21 +00:00
|
|
|
|
/// Get an invited room with the given room id.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// `room_id` - The unique id of the room that should be fetched.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn get_invited_room(&self, room_id: &RoomId) -> Option<room::Invited> {
|
2021-03-05 11:00:43 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_room(room_id)
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.and_then(|room| room::Invited::new(self.clone(), room))
|
2020-12-22 09:47:21 +00:00
|
|
|
|
}
|
2020-10-24 18:01:39 +00:00
|
|
|
|
|
2020-12-22 09:47:21 +00:00
|
|
|
|
/// Get a left room with the given room id.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// `room_id` - The unique id of the room that should be fetched.
|
2021-03-05 13:55:06 +00:00
|
|
|
|
pub fn get_left_room(&self, room_id: &RoomId) -> Option<room::Left> {
|
2021-03-05 11:00:43 +00:00
|
|
|
|
self.store()
|
|
|
|
|
.get_room(room_id)
|
2021-03-05 13:55:06 +00:00
|
|
|
|
.and_then(|room| room::Left::new(self.clone(), room))
|
2020-12-22 09:47:21 +00:00
|
|
|
|
}
|
2020-05-09 11:33:57 +00:00
|
|
|
|
|
2021-03-23 13:27:55 +00:00
|
|
|
|
/// Gets the homeserver’s supported login types.
|
|
|
|
|
///
|
|
|
|
|
/// This should be the first step when trying to login so you can call the
|
|
|
|
|
/// appropriate method for the next step.
|
|
|
|
|
pub async fn get_login_types(&self) -> Result<get_login_types::Response> {
|
|
|
|
|
let request = get_login_types::Request::new();
|
|
|
|
|
self.send(request, None).await
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:47:15 +00:00
|
|
|
|
/// Get the URL to use to login via Single Sign-On.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a URL that should be opened in a web browser to let the user
|
|
|
|
|
/// login.
|
|
|
|
|
///
|
2021-03-23 14:17:12 +00:00
|
|
|
|
/// After a successful login, the loginToken received at the redirect URL should
|
|
|
|
|
/// be used to login with [`login_with_token`].
|
|
|
|
|
///
|
2021-03-23 13:47:15 +00:00
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `redirect_url` - The URL that will receive a `loginToken` after a
|
|
|
|
|
/// successful SSO login.
|
2021-03-23 14:17:12 +00:00
|
|
|
|
///
|
|
|
|
|
/// [`login_with_token`]: #method.login_with_token
|
2021-03-23 13:47:15 +00:00
|
|
|
|
pub fn get_sso_login_url(&self, redirect_url: &str) -> Result<String> {
|
|
|
|
|
let homeserver = self.homeserver();
|
|
|
|
|
let request =
|
|
|
|
|
sso_login::Request::new(redirect_url).try_into_http_request(homeserver.as_str(), None);
|
|
|
|
|
match request {
|
|
|
|
|
Ok(req) => Ok(req.uri().to_string()),
|
|
|
|
|
Err(err) => Err(Error::from(HttpError::from(err))),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Login to the server.
|
|
|
|
|
///
|
2020-07-07 13:47:34 +00:00
|
|
|
|
/// This can be used for the first login as well as for subsequent logins,
|
|
|
|
|
/// note that if the device id isn't provided a new device will be created.
|
|
|
|
|
///
|
|
|
|
|
/// If this isn't the first login a device id should be provided to restore
|
|
|
|
|
/// the correct stores.
|
|
|
|
|
///
|
2020-10-06 09:40:32 +00:00
|
|
|
|
/// Alternatively the [`restore_login`] method can be used to restore a
|
2020-07-07 13:47:34 +00:00
|
|
|
|
/// logged in client without the password.
|
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2020-03-02 10:31:03 +00:00
|
|
|
|
/// * `user` - The user that should be logged in to the homeserver.
|
|
|
|
|
///
|
|
|
|
|
/// * `password` - The password of the user.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - A unique id that will be associated with this session. If
|
2020-03-30 18:18:08 +00:00
|
|
|
|
/// not given the homeserver will create one. Can be an existing
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// device_id from a previous login call. Note that this should be done
|
2020-03-30 18:18:08 +00:00
|
|
|
|
/// only if the client also holds the encryption keys for this device.
|
2020-10-06 09:40:32 +00:00
|
|
|
|
///
|
2020-10-06 10:01:47 +00:00
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::identifiers::DeviceId;
|
|
|
|
|
/// # use matrix_sdk_common::assign;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let user = "example";
|
2020-10-06 10:41:21 +00:00
|
|
|
|
/// let response = client
|
|
|
|
|
/// .login(user, "wordpass", None, Some("My bot")).await
|
|
|
|
|
/// .unwrap();
|
2020-10-06 10:01:47 +00:00
|
|
|
|
///
|
|
|
|
|
/// println!("Logged in as {}, got device_id {} and access_token {}",
|
|
|
|
|
/// user, response.device_id, response.access_token);
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
2020-10-06 09:40:32 +00:00
|
|
|
|
/// [`restore_login`]: #method.restore_login
|
2020-03-19 12:55:04 +00:00
|
|
|
|
#[instrument(skip(password))]
|
2020-08-15 01:09:13 +00:00
|
|
|
|
pub async fn login(
|
2020-04-15 12:29:34 +00:00
|
|
|
|
&self,
|
2020-08-15 01:09:13 +00:00
|
|
|
|
user: &str,
|
|
|
|
|
password: &str,
|
|
|
|
|
device_id: Option<&str>,
|
|
|
|
|
initial_device_display_name: Option<&str>,
|
2020-03-18 13:15:56 +00:00
|
|
|
|
) -> Result<login::Response> {
|
2020-03-19 12:55:04 +00:00
|
|
|
|
info!("Logging in to {} as {:?}", self.homeserver, user);
|
|
|
|
|
|
2020-09-12 01:19:22 +00:00
|
|
|
|
let request = assign!(
|
|
|
|
|
login::Request::new(
|
2021-03-18 10:40:53 +00:00
|
|
|
|
login::LoginInfo::Password { identifier: login::UserIdentifier::MatrixId(user), password },
|
2020-09-12 01:19:22 +00:00
|
|
|
|
), {
|
|
|
|
|
device_id: device_id.map(|d| d.into()),
|
|
|
|
|
initial_device_display_name,
|
|
|
|
|
}
|
|
|
|
|
);
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-05-06 13:36:55 +00:00
|
|
|
|
self.base_client.receive_login_response(&response).await?;
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 14:30:40 +00:00
|
|
|
|
/// Login to the server via Single Sign-On.
|
|
|
|
|
///
|
|
|
|
|
/// This takes care of the whole SSO flow:
|
|
|
|
|
/// * Spawn a local http server
|
|
|
|
|
/// * Provide a callback to open the SSO login URL in a web browser
|
|
|
|
|
/// * Wait for the local http server to get the loginToken
|
|
|
|
|
/// * Call [`login_with_token`]
|
|
|
|
|
///
|
|
|
|
|
/// If cancellation is needed the method should be wrapped in a cancellable
|
|
|
|
|
/// task. **Note** that users with root access to the system have the ability
|
|
|
|
|
/// to snoop in on the data/token that is passed to the local HTTP server
|
|
|
|
|
/// that will be spawned.
|
|
|
|
|
///
|
|
|
|
|
/// If you need more control over the SSO login process, you should use
|
|
|
|
|
/// [`get_sso_login_url`] and [`login_with_token`] directly.
|
|
|
|
|
///
|
|
|
|
|
/// This should only be used for the first login.
|
|
|
|
|
///
|
|
|
|
|
/// The [`restore_login`] method should be used to restore a
|
|
|
|
|
/// logged in client after the first login.
|
|
|
|
|
///
|
|
|
|
|
/// A device id should be provided to restore the correct stores, if the
|
|
|
|
|
/// device id isn't provided a new device will be created.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `use_sso_login_url` - A callback that will receive the SSO Login URL. It
|
|
|
|
|
/// should usually be used to open the SSO URL in a browser and must return
|
|
|
|
|
/// `Ok(())` if the URL was successfully opened. If it returns `Err`, the
|
|
|
|
|
/// error will be forwarded.
|
|
|
|
|
///
|
|
|
|
|
/// * `server_url` - The local URL the server is going to try to bind to, e.g.
|
|
|
|
|
/// `http://localhost:3030`. If `None`, the server will try to open a random
|
|
|
|
|
/// port on localhost.
|
|
|
|
|
///
|
|
|
|
|
/// * `server_response` - The text that will be shown on the webpage at the end
|
|
|
|
|
/// of the login process. This can be an HTML page. If `None`, a default
|
|
|
|
|
/// text will be displayed.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - A unique id that will be associated with this session. If
|
|
|
|
|
/// not given the homeserver will create one. Can be an existing device_id
|
|
|
|
|
/// from a previous login call. Note that this should be provided only
|
|
|
|
|
/// if the client also holds the encryption keys for this device.
|
|
|
|
|
///
|
|
|
|
|
/// * `initial_device_display_name` - A public display name that will be
|
|
|
|
|
/// associated with the device_id. Only necessary the first time you
|
|
|
|
|
/// login with this device_id. It can be changed later.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("https://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// let response = client
|
|
|
|
|
/// .login_with_sso(
|
|
|
|
|
/// |sso_url| async move {
|
|
|
|
|
/// // Open sso_url
|
|
|
|
|
/// Ok(())
|
|
|
|
|
/// },
|
|
|
|
|
/// None,
|
|
|
|
|
/// None,
|
|
|
|
|
/// None,
|
|
|
|
|
/// Some("My app")
|
|
|
|
|
/// )
|
|
|
|
|
/// .await
|
|
|
|
|
/// .unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// println!("Logged in as {}, got device_id {} and access_token {}",
|
|
|
|
|
/// response.user_id, response.device_id, response.access_token);
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`get_sso_login_url`]: #method.get_sso_login_url
|
|
|
|
|
/// [`login_with_token`]: #method.login_with_token
|
|
|
|
|
/// [`restore_login`]: #method.restore_login
|
|
|
|
|
#[cfg(all(feature = "sso_login", not(target_arch = "wasm32")))]
|
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "docs",
|
|
|
|
|
doc(cfg(all(sso_login, not(target_arch = "wasm32"))))
|
|
|
|
|
)]
|
|
|
|
|
pub async fn login_with_sso<C>(
|
|
|
|
|
&self,
|
|
|
|
|
use_sso_login_url: impl Fn(String) -> C,
|
|
|
|
|
server_url: Option<&str>,
|
|
|
|
|
server_response: Option<&str>,
|
|
|
|
|
device_id: Option<&str>,
|
|
|
|
|
initial_device_display_name: Option<&str>,
|
|
|
|
|
) -> Result<login::Response>
|
|
|
|
|
where
|
|
|
|
|
C: Future<Output = Result<()>>,
|
|
|
|
|
{
|
|
|
|
|
info!("Logging in to {}", self.homeserver);
|
|
|
|
|
let (signal_tx, signal_rx) = oneshot::channel();
|
|
|
|
|
let (data_tx, data_rx) = oneshot::channel();
|
|
|
|
|
let data_tx_mutex = Arc::new(std::sync::Mutex::new(Some(data_tx)));
|
|
|
|
|
|
|
|
|
|
let mut redirect_url = match server_url {
|
|
|
|
|
Some(s) => match Url::parse(s) {
|
|
|
|
|
Ok(url) => url,
|
|
|
|
|
Err(err) => return Err(IoError::new(IoErrorKind::InvalidData, err).into()),
|
|
|
|
|
},
|
|
|
|
|
None => Url::parse("http://localhost:0/").unwrap(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let response = match server_response {
|
|
|
|
|
Some(s) => s.to_string(),
|
|
|
|
|
None => String::from(
|
|
|
|
|
"The Single Sign-On login process is complete. You can close this page now.",
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let route = warp::get()
|
|
|
|
|
.and(warp::query::<HashMap<String, String>>())
|
|
|
|
|
.map(move |p: HashMap<String, String>| {
|
|
|
|
|
if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() {
|
|
|
|
|
if let Some(token) = p.get("loginToken") {
|
|
|
|
|
data_tx.send(Some(token.to_owned())).unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
data_tx.send(None).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Response::builder().body(response.clone())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let listener = {
|
|
|
|
|
if redirect_url
|
|
|
|
|
.port()
|
|
|
|
|
.expect("The redirect URL doesn't include a port")
|
|
|
|
|
== 0
|
|
|
|
|
{
|
|
|
|
|
let host = redirect_url
|
|
|
|
|
.host_str()
|
|
|
|
|
.expect("The redirect URL doesn't have a host");
|
|
|
|
|
let mut n = 0u8;
|
|
|
|
|
let mut port = 0u16;
|
|
|
|
|
let mut res = Err(IoError::new(IoErrorKind::Other, ""));
|
|
|
|
|
let mut rng = thread_rng();
|
|
|
|
|
|
|
|
|
|
while res.is_err() && n < SSO_SERVER_BIND_TRIES {
|
|
|
|
|
port = rng.gen_range(SSO_SERVER_BIND_RANGE);
|
|
|
|
|
res = TcpListener::bind((host, port)).await;
|
|
|
|
|
n += 1;
|
|
|
|
|
}
|
|
|
|
|
match res {
|
|
|
|
|
Ok(s) => {
|
|
|
|
|
redirect_url
|
|
|
|
|
.set_port(Some(port))
|
|
|
|
|
.expect("Could not set new port on redirect URL");
|
|
|
|
|
s
|
|
|
|
|
}
|
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
match TcpListener::bind(redirect_url.as_str()).await {
|
|
|
|
|
Ok(s) => s,
|
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let server = warp::serve(route).serve_incoming_with_graceful_shutdown(
|
|
|
|
|
TcpListenerStream::new(listener),
|
|
|
|
|
async {
|
|
|
|
|
signal_rx.await.ok();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
tokio::spawn(server);
|
|
|
|
|
|
|
|
|
|
let sso_url = self.get_sso_login_url(redirect_url.as_str()).unwrap();
|
|
|
|
|
|
|
|
|
|
match use_sso_login_url(sso_url).await {
|
|
|
|
|
Ok(t) => t,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let token = match data_rx.await {
|
|
|
|
|
Ok(Some(t)) => t,
|
|
|
|
|
Ok(None) => {
|
|
|
|
|
return Err(IoError::new(IoErrorKind::Other, "Could not get the loginToken").into())
|
|
|
|
|
}
|
|
|
|
|
Err(err) => return Err(IoError::new(IoErrorKind::Other, format!("{}", err)).into()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let _ = signal_tx.send(());
|
|
|
|
|
|
|
|
|
|
self.login_with_token(token.as_str(), device_id, initial_device_display_name)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 14:17:12 +00:00
|
|
|
|
/// Login to the server with a token.
|
|
|
|
|
///
|
|
|
|
|
/// This token is usually received in the SSO flow after following the URL
|
|
|
|
|
/// provided by [`get_sso_login_url`], note that this is not the access token
|
|
|
|
|
/// of a session.
|
|
|
|
|
///
|
|
|
|
|
/// This should only be used for the first login.
|
|
|
|
|
///
|
|
|
|
|
/// The [`restore_login`] method should be used to restore a
|
|
|
|
|
/// logged in client after the first login.
|
|
|
|
|
///
|
|
|
|
|
/// A device id should be provided to restore the correct stores, if the
|
|
|
|
|
/// device id isn't provided a new device will be created.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `token` - A login token.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - A unique id that will be associated with this session. If
|
|
|
|
|
/// not given the homeserver will create one. Can be an existing device_id
|
|
|
|
|
/// from a previous login call. Note that this should be provided only
|
|
|
|
|
/// if the client also holds the encryption keys for this device.
|
|
|
|
|
///
|
|
|
|
|
/// * `initial_device_display_name` - A public display name that will be
|
|
|
|
|
/// associated with the device_id. Only necessary the first time you
|
|
|
|
|
/// login with this device_id. It can be changed later.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::identifiers::DeviceId;
|
|
|
|
|
/// # use matrix_sdk_common::assign;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("https://example.com").unwrap();
|
|
|
|
|
/// # let redirect_url = "http://localhost:1234";
|
|
|
|
|
/// # let login_token = "token";
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let sso_url = client.get_sso_login_url(redirect_url);
|
|
|
|
|
///
|
|
|
|
|
/// // Let the user authenticate at the SSO URL
|
|
|
|
|
/// // Receive the loginToken param at redirect_url
|
|
|
|
|
///
|
|
|
|
|
/// let response = client
|
|
|
|
|
/// .login_with_token(login_token, None, Some("My app")).await
|
|
|
|
|
/// .unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// println!("Logged in as {}, got device_id {} and access_token {}",
|
|
|
|
|
/// response.user_id, response.device_id, response.access_token);
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`get_sso_login_url`]: #method.get_sso_login_url
|
|
|
|
|
/// [`restore_login`]: #method.restore_login
|
|
|
|
|
#[instrument(skip(token))]
|
|
|
|
|
pub async fn login_with_token(
|
|
|
|
|
&self,
|
|
|
|
|
token: &str,
|
|
|
|
|
device_id: Option<&str>,
|
|
|
|
|
initial_device_display_name: Option<&str>,
|
|
|
|
|
) -> Result<login::Response> {
|
|
|
|
|
info!("Logging in to {}", self.homeserver);
|
|
|
|
|
|
|
|
|
|
let request = assign!(
|
|
|
|
|
login::Request::new(
|
|
|
|
|
login::LoginInfo::Token { token },
|
2020-09-12 01:19:22 +00:00
|
|
|
|
), {
|
|
|
|
|
device_id: device_id.map(|d| d.into()),
|
|
|
|
|
initial_device_display_name,
|
|
|
|
|
}
|
|
|
|
|
);
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-05-06 13:36:55 +00:00
|
|
|
|
self.base_client.receive_login_response(&response).await?;
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-22 13:23:58 +00:00
|
|
|
|
/// Restore a previously logged in session.
|
|
|
|
|
///
|
2020-07-07 13:47:34 +00:00
|
|
|
|
/// This can be used to restore the client to a logged in state, loading all
|
|
|
|
|
/// the stored state and encryption keys.
|
|
|
|
|
///
|
2020-10-06 09:40:32 +00:00
|
|
|
|
/// Alternatively, if the whole session isn't stored the [`login`] method
|
2020-07-07 13:47:34 +00:00
|
|
|
|
/// can be used with a device id.
|
|
|
|
|
///
|
2020-05-22 13:23:58 +00:00
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2020-06-01 11:50:45 +00:00
|
|
|
|
/// * `session` - A session that the user already has from a
|
2020-05-22 13:23:58 +00:00
|
|
|
|
/// previous login call.
|
2020-10-06 09:40:32 +00:00
|
|
|
|
///
|
|
|
|
|
/// [`login`]: #method.login
|
2020-05-22 13:23:58 +00:00
|
|
|
|
pub async fn restore_login(&self, session: Session) -> Result<()> {
|
|
|
|
|
Ok(self.base_client.restore_login(session).await?)
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 11:55:24 +00:00
|
|
|
|
/// Register a user to the server.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2020-08-26 20:30:29 +00:00
|
|
|
|
/// * `registration` - The easiest way to create this request is using the `register::Request`
|
|
|
|
|
/// itself.
|
2020-06-02 11:55:24 +00:00
|
|
|
|
///
|
2020-06-02 18:57:56 +00:00
|
|
|
|
///
|
2020-06-02 11:55:24 +00:00
|
|
|
|
/// # Examples
|
2020-08-12 15:15:18 +00:00
|
|
|
|
/// ```no_run
|
2020-06-02 18:57:56 +00:00
|
|
|
|
/// # use std::convert::TryFrom;
|
2020-08-26 11:40:38 +00:00
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::api::r0::account::register::{Request as RegistrationRequest, RegistrationKind};
|
2020-08-20 01:42:01 +00:00
|
|
|
|
/// # use matrix_sdk::api::r0::uiaa::AuthData;
|
2020-06-02 18:57:56 +00:00
|
|
|
|
/// # use matrix_sdk::identifiers::DeviceId;
|
2020-08-26 11:40:38 +00:00
|
|
|
|
/// # use matrix_sdk_common::assign;
|
2020-08-12 15:15:18 +00:00
|
|
|
|
/// # use futures::executor::block_on;
|
2020-06-02 18:57:56 +00:00
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
2020-08-12 15:15:18 +00:00
|
|
|
|
/// # block_on(async {
|
2020-08-26 11:40:38 +00:00
|
|
|
|
///
|
|
|
|
|
/// let request = assign!(RegistrationRequest::new(), {
|
|
|
|
|
/// username: Some("user"),
|
|
|
|
|
/// password: Some("password"),
|
|
|
|
|
/// auth: Some(AuthData::FallbackAcknowledgement { session: "foobar" }),
|
|
|
|
|
/// });
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// client.register(request).await;
|
2020-06-02 18:57:56 +00:00
|
|
|
|
/// # })
|
2020-06-02 11:55:24 +00:00
|
|
|
|
/// ```
|
|
|
|
|
#[instrument(skip(registration))]
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub async fn register(
|
2020-06-02 11:55:24 +00:00
|
|
|
|
&self,
|
2020-08-26 11:40:38 +00:00
|
|
|
|
registration: impl Into<register::Request<'_>>,
|
2020-06-02 11:55:24 +00:00
|
|
|
|
) -> Result<register::Response> {
|
|
|
|
|
info!("Registering to {}", self.homeserver);
|
|
|
|
|
|
|
|
|
|
let request = registration.into();
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-06-02 11:55:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-22 20:25:31 +00:00
|
|
|
|
/// Get or upload a sync filter.
|
|
|
|
|
pub async fn get_or_upload_filter(
|
|
|
|
|
&self,
|
|
|
|
|
filter_name: &str,
|
|
|
|
|
definition: FilterDefinition<'_>,
|
|
|
|
|
) -> Result<String> {
|
2021-01-18 16:31:33 +00:00
|
|
|
|
if let Some(filter) = self.base_client.get_filter(filter_name).await? {
|
2020-11-22 20:25:31 +00:00
|
|
|
|
Ok(filter)
|
|
|
|
|
} else {
|
|
|
|
|
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let request = FilterUploadRequest::new(&user_id, definition);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-11-22 20:25:31 +00:00
|
|
|
|
|
|
|
|
|
self.base_client
|
|
|
|
|
.receive_filter_upload(filter_name, &response)
|
2021-01-18 16:31:33 +00:00
|
|
|
|
.await?;
|
2020-11-22 20:25:31 +00:00
|
|
|
|
|
|
|
|
|
Ok(response.filter_id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 20:32:28 +00:00
|
|
|
|
/// Join a room by `RoomId`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a `join_room_by_id::Response` consisting of the
|
|
|
|
|
/// joined rooms `RoomId`.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2020-05-06 12:36:28 +00:00
|
|
|
|
/// * `room_id` - The `RoomId` of the room to be joined.
|
|
|
|
|
pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<join_room_by_id::Response> {
|
2020-08-15 01:09:13 +00:00
|
|
|
|
let request = join_room_by_id::Request::new(room_id);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-04-10 20:32:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 10:32:36 +00:00
|
|
|
|
/// Join a room by `RoomId`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a `join_room_by_id_or_alias::Response` consisting of the
|
|
|
|
|
/// joined rooms `RoomId`.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `alias` - The `RoomId` or `RoomAliasId` of the room to be joined.
|
2020-06-01 11:50:45 +00:00
|
|
|
|
/// An alias looks like `#name:example.com`.
|
2020-05-15 10:32:36 +00:00
|
|
|
|
pub async fn join_room_by_id_or_alias(
|
|
|
|
|
&self,
|
|
|
|
|
alias: &RoomIdOrAliasId,
|
2020-07-22 18:43:47 +00:00
|
|
|
|
server_names: &[Box<ServerName>],
|
2020-05-15 10:32:36 +00:00
|
|
|
|
) -> Result<join_room_by_id_or_alias::Response> {
|
2020-08-26 11:40:38 +00:00
|
|
|
|
let request = assign!(join_room_by_id_or_alias::Request::new(alias), {
|
2020-08-15 01:09:13 +00:00
|
|
|
|
server_name: server_names,
|
2020-08-26 11:40:38 +00:00
|
|
|
|
});
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-05-15 10:32:36 +00:00
|
|
|
|
}
|
2020-04-10 20:32:28 +00:00
|
|
|
|
|
2020-06-23 21:31:58 +00:00
|
|
|
|
/// Search the homeserver's directory of public rooms.
|
|
|
|
|
///
|
2020-06-25 12:31:51 +00:00
|
|
|
|
/// Sends a request to "_matrix/client/r0/publicRooms", returns
|
|
|
|
|
/// a `get_public_rooms::Response`.
|
2020-06-23 21:31:58 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `limit` - The number of `PublicRoomsChunk`s in each response.
|
|
|
|
|
///
|
|
|
|
|
/// * `since` - Pagination token from a previous request.
|
|
|
|
|
///
|
|
|
|
|
/// * `server` - The name of the server, if `None` the requested server is used.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
/// ```no_run
|
2020-06-24 11:46:40 +00:00
|
|
|
|
/// use matrix_sdk::Client;
|
2020-09-29 15:23:14 +00:00
|
|
|
|
/// # use std::convert::TryInto;
|
2020-06-23 21:31:58 +00:00
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # let limit = Some(10);
|
|
|
|
|
/// # let since = Some("since token");
|
2020-09-29 15:23:14 +00:00
|
|
|
|
/// # let server = Some("servername.com".try_into().unwrap());
|
2020-06-23 21:31:58 +00:00
|
|
|
|
///
|
2020-08-12 13:23:44 +00:00
|
|
|
|
/// let mut client = Client::new(homeserver).unwrap();
|
2020-06-23 21:31:58 +00:00
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # block_on(async {
|
2020-06-25 12:31:51 +00:00
|
|
|
|
///
|
2020-08-12 13:23:44 +00:00
|
|
|
|
/// client.public_rooms(limit, since, server).await;
|
2020-06-23 21:31:58 +00:00
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
2020-06-24 11:46:40 +00:00
|
|
|
|
pub async fn public_rooms(
|
2020-06-23 21:31:58 +00:00
|
|
|
|
&self,
|
|
|
|
|
limit: Option<u32>,
|
|
|
|
|
since: Option<&str>,
|
2020-09-29 15:23:14 +00:00
|
|
|
|
server: Option<&ServerName>,
|
2020-06-23 21:31:58 +00:00
|
|
|
|
) -> Result<get_public_rooms::Response> {
|
2020-12-08 10:01:20 +00:00
|
|
|
|
let limit = limit.map(UInt::from);
|
2020-06-23 21:31:58 +00:00
|
|
|
|
|
2020-09-12 01:19:22 +00:00
|
|
|
|
let request = assign!(get_public_rooms::Request::new(), {
|
2020-06-23 21:31:58 +00:00
|
|
|
|
limit,
|
|
|
|
|
since,
|
|
|
|
|
server,
|
2020-09-12 01:19:22 +00:00
|
|
|
|
});
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-06-23 21:31:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// Create a room using the `RoomBuilder` and send the request.
|
2020-04-10 20:32:28 +00:00
|
|
|
|
///
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// Sends a request to `/_matrix/client/r0/createRoom`, returns a `create_room::Response`,
|
|
|
|
|
/// this is an empty response.
|
2020-04-10 20:32:28 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2020-08-26 20:30:29 +00:00
|
|
|
|
/// * `room` - The easiest way to create this request is using the
|
|
|
|
|
/// `create_room::Request` itself.
|
2020-04-10 20:32:28 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// ```no_run
|
2020-08-26 11:40:38 +00:00
|
|
|
|
/// use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::api::r0::room::{create_room::Request as CreateRoomRequest, Visibility};
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # use url::Url;
|
2020-04-14 10:42:42 +00:00
|
|
|
|
///
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
2020-08-26 11:40:38 +00:00
|
|
|
|
/// let request = CreateRoomRequest::new();
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # block_on(async {
|
2020-08-26 11:40:38 +00:00
|
|
|
|
/// assert!(client.create_room(request).await.is_ok());
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # });
|
2020-04-10 20:32:28 +00:00
|
|
|
|
/// ```
|
2020-08-26 11:40:38 +00:00
|
|
|
|
pub async fn create_room(
|
2020-04-15 12:29:34 +00:00
|
|
|
|
&self,
|
2020-08-26 11:40:38 +00:00
|
|
|
|
room: impl Into<create_room::Request<'_>>,
|
2020-04-10 20:32:28 +00:00
|
|
|
|
) -> Result<create_room::Response> {
|
|
|
|
|
let request = room.into();
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-04-10 20:32:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// Search the homeserver's directory of public rooms with a filter.
|
|
|
|
|
///
|
|
|
|
|
/// Sends a request to "_matrix/client/r0/publicRooms", returns
|
|
|
|
|
/// a `get_public_rooms_filtered::Response`.
|
2020-04-11 12:46:45 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// * `room_search` - The easiest way to create this request is using the
|
|
|
|
|
/// `get_public_rooms_filtered::Request` itself.
|
2020-04-13 18:46:54 +00:00
|
|
|
|
///
|
2020-04-13 18:08:51 +00:00
|
|
|
|
/// # Examples
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::convert::TryFrom;
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::directory::{Filter, RoomNetwork};
|
|
|
|
|
/// # use matrix_sdk::api::r0::directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest;
|
|
|
|
|
/// # use matrix_sdk_common::assign;
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
2020-04-14 10:36:03 +00:00
|
|
|
|
/// # block_on(async {
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// let mut client = Client::new(homeserver).unwrap();
|
2021-03-13 08:16:48 +00:00
|
|
|
|
///
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// let generic_search_term = Some("matrix-rust-sdk");
|
|
|
|
|
/// let filter = assign!(Filter::new(), { generic_search_term });
|
|
|
|
|
/// let request = assign!(PublicRoomsFilterRequest::new(), { filter });
|
2021-03-13 08:16:48 +00:00
|
|
|
|
///
|
2021-03-08 23:12:59 +00:00
|
|
|
|
/// client.public_rooms_filtered(request).await;
|
2021-03-13 08:16:48 +00:00
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
2021-03-08 23:12:59 +00:00
|
|
|
|
pub async fn public_rooms_filtered(
|
2021-03-13 08:16:48 +00:00
|
|
|
|
&self,
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room_search: impl Into<get_public_rooms_filtered::Request<'_>>,
|
|
|
|
|
) -> Result<get_public_rooms_filtered::Response> {
|
|
|
|
|
let request = room_search.into();
|
2021-03-13 08:16:48 +00:00
|
|
|
|
self.send(request, None).await
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:59:31 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2021-03-08 23:12:59 +00:00
|
|
|
|
pub(crate) async fn room_send_helper(
|
|
|
|
|
&self,
|
|
|
|
|
request: &RoomMessageRequest,
|
|
|
|
|
) -> Result<send_message_event::Response> {
|
|
|
|
|
let content = request.content.clone();
|
|
|
|
|
let txn_id = request.txn_id;
|
|
|
|
|
let room_id = &request.room_id;
|
2020-09-14 18:07:55 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
self.get_joined_room(room_id)
|
|
|
|
|
.expect("Can't send a message to a room that isn't known to the store")
|
|
|
|
|
.send(content, Some(txn_id))
|
|
|
|
|
.await
|
2020-09-14 18:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:16:16 +00:00
|
|
|
|
/// Upload some media to the server.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `content_type` - The type of the media, this will be used as the
|
|
|
|
|
/// content-type header.
|
|
|
|
|
///
|
|
|
|
|
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
|
|
|
|
/// media.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::{path::PathBuf, fs::File, io::Read};
|
|
|
|
|
/// # use matrix_sdk::{Client, identifiers::room_id};
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
2020-10-13 09:00:52 +00:00
|
|
|
|
/// # use mime;
|
2020-09-15 15:16:16 +00:00
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let path = PathBuf::from("/home/example/my-cat.jpg");
|
|
|
|
|
/// let mut image = File::open(path).unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// let response = client
|
2020-10-13 09:00:52 +00:00
|
|
|
|
/// .upload(&mime::IMAGE_JPEG, &mut image)
|
2020-09-15 15:16:16 +00:00
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Can't upload my cat.");
|
|
|
|
|
///
|
|
|
|
|
/// println!("Cat URI: {}", response.content_uri);
|
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn upload(
|
|
|
|
|
&self,
|
2020-10-13 09:00:52 +00:00
|
|
|
|
content_type: &Mime,
|
2020-09-15 15:16:16 +00:00
|
|
|
|
reader: &mut impl Read,
|
|
|
|
|
) -> Result<create_content::Response> {
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
reader.read_to_end(&mut data)?;
|
|
|
|
|
|
2021-02-01 20:56:15 +00:00
|
|
|
|
let timeout = std::cmp::max(
|
|
|
|
|
Duration::from_secs(data.len() as u64 / DEFAULT_UPLOAD_SPEED),
|
|
|
|
|
MIN_UPLOAD_REQUEST_TIMEOUT,
|
|
|
|
|
);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
|
2020-10-18 00:01:39 +00:00
|
|
|
|
let request = assign!(create_content::Request::new(data), {
|
|
|
|
|
content_type: Some(content_type.essence_str()),
|
|
|
|
|
});
|
2020-09-14 18:07:55 +00:00
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
let request_config = self.http_client.request_config.timeout(timeout);
|
|
|
|
|
Ok(self
|
|
|
|
|
.http_client
|
|
|
|
|
.upload(request, Some(request_config))
|
|
|
|
|
.await?)
|
2020-09-14 18:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:00:20 +00:00
|
|
|
|
/// Send a room message to a room.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the parsed response from the server.
|
|
|
|
|
///
|
|
|
|
|
/// If the encryption feature is enabled this method will transparently
|
|
|
|
|
/// encrypt the room message if this room is encrypted.
|
|
|
|
|
///
|
|
|
|
|
/// **Note**: This method will send an unencrypted message if the room cannot
|
|
|
|
|
/// be found in the store, prefer the higher level
|
|
|
|
|
/// [send()](room::Joined::send()) method that can be found for the
|
|
|
|
|
/// [Joined](room::Joined) room struct to avoid this.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `room_id` - The unique id of the room.
|
|
|
|
|
///
|
|
|
|
|
/// * `content` - The content of the message event.
|
|
|
|
|
///
|
|
|
|
|
/// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent`
|
|
|
|
|
/// held in its unsigned field as `transaction_id`. If not given one is
|
|
|
|
|
/// created for the message.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::sync::{Arc, RwLock};
|
|
|
|
|
/// # use matrix_sdk::{Client, SyncSettings};
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use matrix_sdk::identifiers::room_id;
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// use matrix_sdk::events::{
|
|
|
|
|
/// AnyMessageEventContent,
|
|
|
|
|
/// room::message::{MessageEventContent, TextMessageEventContent},
|
|
|
|
|
/// };
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// # let room_id = room_id!("!test:localhost");
|
|
|
|
|
/// use matrix_sdk_common::uuid::Uuid;
|
|
|
|
|
///
|
|
|
|
|
/// let content = AnyMessageEventContent::RoomMessage(
|
|
|
|
|
/// MessageEventContent::text_plain("Hello world")
|
|
|
|
|
/// );
|
|
|
|
|
///
|
|
|
|
|
/// let txn_id = Uuid::new_v4();
|
2021-03-23 13:29:26 +00:00
|
|
|
|
/// client.room_send(&room_id, content, Some(txn_id)).await.unwrap();
|
2021-03-23 13:00:20 +00:00
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn room_send(
|
|
|
|
|
&self,
|
|
|
|
|
room_id: &RoomId,
|
|
|
|
|
content: impl Into<AnyMessageEventContent>,
|
|
|
|
|
txn_id: Option<Uuid>,
|
|
|
|
|
) -> Result<send_message_event::Response> {
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
if let Some(room) = self.get_joined_room(room_id) {
|
|
|
|
|
room.send(content, txn_id).await
|
|
|
|
|
} else {
|
|
|
|
|
let content = content.into();
|
|
|
|
|
let txn_id = txn_id.unwrap_or_else(Uuid::new_v4).to_string();
|
|
|
|
|
let request = send_message_event::Request::new(room_id, &txn_id, &content);
|
|
|
|
|
|
|
|
|
|
self.send(request, None).await
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-09 14:28:24 +00:00
|
|
|
|
/// Send an arbitrary request to the server, without updating client state.
|
|
|
|
|
///
|
|
|
|
|
/// **Warning:** Because this method *does not* update the client state, it is
|
|
|
|
|
/// important to make sure than you account for this yourself, and use wrapper methods
|
|
|
|
|
/// where available. This method should *only* be used if a wrapper method for the
|
|
|
|
|
/// endpoint you'd like to use is not available.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `request` - A filled out and valid request for the endpoint to be hit
|
|
|
|
|
///
|
2021-02-01 16:15:29 +00:00
|
|
|
|
/// * `timeout` - An optional request timeout setting, this overrides the
|
|
|
|
|
/// default request setting if one was set.
|
|
|
|
|
///
|
2020-06-09 14:28:24 +00:00
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use matrix_sdk::{Client, SyncSettings};
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// use matrix_sdk::api::r0::profile;
|
2020-08-04 22:56:26 +00:00
|
|
|
|
/// use matrix_sdk::identifiers::user_id;
|
2020-06-09 14:28:24 +00:00
|
|
|
|
///
|
|
|
|
|
/// // First construct the request you want to make
|
|
|
|
|
/// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html
|
|
|
|
|
/// // for all available Endpoints
|
2020-09-12 01:19:22 +00:00
|
|
|
|
/// let user_id = user_id!("@example:localhost");
|
|
|
|
|
/// let request = profile::get_profile::Request::new(&user_id);
|
2020-06-09 14:28:24 +00:00
|
|
|
|
///
|
|
|
|
|
/// // Start the request using Client::send()
|
2021-02-01 16:15:29 +00:00
|
|
|
|
/// let response = client.send(request, None).await.unwrap();
|
2020-06-09 14:28:24 +00:00
|
|
|
|
///
|
|
|
|
|
/// // Check the corresponding Response struct to find out what types are
|
|
|
|
|
/// // returned
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
2021-02-01 16:15:29 +00:00
|
|
|
|
pub async fn send<Request>(
|
|
|
|
|
&self,
|
|
|
|
|
request: Request,
|
2021-04-01 17:25:31 +00:00
|
|
|
|
config: Option<RequestConfig>,
|
2021-02-01 16:15:29 +00:00
|
|
|
|
) -> Result<Request::IncomingResponse>
|
2020-07-31 18:35:27 +00:00
|
|
|
|
where
|
2020-08-15 01:09:13 +00:00
|
|
|
|
Request: OutgoingRequest + Debug,
|
2021-01-31 17:09:03 +00:00
|
|
|
|
HttpError: From<FromHttpResponseError<Request::EndpointError>>,
|
2020-07-31 18:35:27 +00:00
|
|
|
|
{
|
2021-04-01 17:25:31 +00:00
|
|
|
|
Ok(self.http_client.send(request, config).await?)
|
2020-06-01 11:50:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-12 17:18:30 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-12-15 15:35:54 +00:00
|
|
|
|
pub(crate) async fn send_to_device(
|
|
|
|
|
&self,
|
|
|
|
|
request: &ToDeviceRequest,
|
|
|
|
|
) -> Result<ToDeviceResponse> {
|
2020-09-03 18:02:55 +00:00
|
|
|
|
let txn_id_string = request.txn_id_string();
|
2020-10-01 14:31:24 +00:00
|
|
|
|
let request = RumaToDeviceRequest::new(
|
|
|
|
|
request.event_type.clone(),
|
|
|
|
|
&txn_id_string,
|
|
|
|
|
request.messages.clone(),
|
|
|
|
|
);
|
2020-08-12 09:39:47 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-08-12 09:39:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-05 16:04:15 +00:00
|
|
|
|
/// Get information of all our own devices.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use matrix_sdk::{Client, SyncSettings};
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let response = client.devices().await.expect("Can't get devices from server");
|
|
|
|
|
///
|
|
|
|
|
/// for device in response.devices {
|
|
|
|
|
/// println!(
|
|
|
|
|
/// "Device: {} {}",
|
|
|
|
|
/// device.device_id,
|
|
|
|
|
/// device.display_name.as_deref().unwrap_or("")
|
|
|
|
|
/// );
|
|
|
|
|
/// }
|
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
pub async fn devices(&self) -> Result<get_devices::Response> {
|
2020-09-12 01:19:22 +00:00
|
|
|
|
let request = get_devices::Request::new();
|
2020-09-05 16:04:15 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-09-05 16:04:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 15:27:00 +00:00
|
|
|
|
/// Delete the given devices from the server.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `devices` - The list of devices that should be deleted from the
|
|
|
|
|
/// server.
|
|
|
|
|
///
|
|
|
|
|
/// * `auth_data` - This request requires user interactive auth, the first
|
|
|
|
|
/// request needs to set this to `None` and will always fail with an
|
|
|
|
|
/// `UiaaResponse`. The response will contain information for the
|
|
|
|
|
/// interactive auth and the same request needs to be made but this time
|
|
|
|
|
/// with some `auth_data` provided.
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use matrix_sdk::{
|
|
|
|
|
/// # api::r0::uiaa::{UiaaResponse, AuthData},
|
|
|
|
|
/// # Client, SyncSettings, Error, FromHttpResponseError, ServerError,
|
|
|
|
|
/// # };
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use serde_json::json;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use std::{collections::BTreeMap, convert::TryFrom};
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let devices = &["DEVICEID".into()];
|
|
|
|
|
///
|
|
|
|
|
/// if let Err(e) = client.delete_devices(devices, None).await {
|
|
|
|
|
/// if let Some(info) = e.uiaa_response() {
|
|
|
|
|
/// let mut auth_parameters = BTreeMap::new();
|
|
|
|
|
///
|
|
|
|
|
/// let identifier = json!({
|
|
|
|
|
/// "type": "m.id.user",
|
|
|
|
|
/// "user": "example",
|
|
|
|
|
/// });
|
|
|
|
|
/// auth_parameters.insert("identifier".to_owned(), identifier);
|
|
|
|
|
/// auth_parameters.insert("password".to_owned(), "wordpass".into());
|
|
|
|
|
///
|
|
|
|
|
/// // This is needed because of https://github.com/matrix-org/synapse/issues/5665
|
|
|
|
|
/// auth_parameters.insert("user".to_owned(), "@example:localhost".into());
|
|
|
|
|
///
|
|
|
|
|
/// let auth_data = AuthData::DirectRequest {
|
|
|
|
|
/// kind: "m.login.password",
|
|
|
|
|
/// auth_parameters,
|
|
|
|
|
/// session: info.session.as_deref(),
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// client
|
|
|
|
|
/// .delete_devices(devices, Some(auth_data))
|
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Can't delete devices");
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
/// # });
|
|
|
|
|
pub async fn delete_devices(
|
|
|
|
|
&self,
|
|
|
|
|
devices: &[DeviceIdBox],
|
|
|
|
|
auth_data: Option<AuthData<'_>>,
|
|
|
|
|
) -> Result<delete_devices::Response> {
|
|
|
|
|
let mut request = delete_devices::Request::new(devices);
|
|
|
|
|
request.auth = auth_data;
|
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await
|
2020-10-16 15:27:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-30 18:18:08 +00:00
|
|
|
|
/// Synchronize the client's state with the latest state on the server.
|
2020-02-21 13:29:25 +00:00
|
|
|
|
///
|
2020-10-06 09:37:29 +00:00
|
|
|
|
/// **Note**: You should not use this method to repeatedly sync if encryption
|
|
|
|
|
/// support is enabled, the [`sync`] method will make additional
|
|
|
|
|
/// requests between syncs that are needed for E2E encryption to work.
|
2020-04-23 23:37:27 +00:00
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `sync_settings` - Settings for the sync call.
|
2020-10-06 09:37:29 +00:00
|
|
|
|
///
|
|
|
|
|
/// [`sync`]: #method.sync
|
2020-03-19 12:55:04 +00:00
|
|
|
|
#[instrument]
|
2020-11-21 21:48:27 +00:00
|
|
|
|
pub async fn sync_once(&self, sync_settings: SyncSettings<'_>) -> Result<SyncResponse> {
|
2020-08-26 11:40:38 +00:00
|
|
|
|
let request = assign!(sync_events::Request::new(), {
|
2020-11-11 13:43:49 +00:00
|
|
|
|
filter: sync_settings.filter.as_ref(),
|
2020-08-15 01:09:13 +00:00
|
|
|
|
since: sync_settings.token.as_deref(),
|
2019-10-23 20:47:00 +00:00
|
|
|
|
full_state: sync_settings.full_state,
|
2020-11-11 13:43:49 +00:00
|
|
|
|
set_presence: &PresenceState::Online,
|
2019-10-23 20:47:00 +00:00
|
|
|
|
timeout: sync_settings.timeout,
|
2020-08-26 11:40:38 +00:00
|
|
|
|
});
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
let request_config = self.http_client.request_config.timeout(
|
|
|
|
|
sync_settings
|
|
|
|
|
.timeout
|
|
|
|
|
.unwrap_or_else(|| Duration::from_secs(0))
|
|
|
|
|
+ self.http_client.request_config.timeout,
|
|
|
|
|
);
|
2021-02-01 16:15:29 +00:00
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
let response = self.send(request, Some(request_config)).await?;
|
2021-03-17 10:53:18 +00:00
|
|
|
|
let sync_response = self.base_client.receive_sync_response(response).await?;
|
|
|
|
|
|
|
|
|
|
if let Some(handler) = self.event_handler.read().await.as_ref() {
|
|
|
|
|
handler.handle_sync(&sync_response).await;
|
|
|
|
|
}
|
2019-10-23 20:47:00 +00:00
|
|
|
|
|
2021-03-17 10:53:18 +00:00
|
|
|
|
Ok(sync_response)
|
2020-05-05 20:13:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 09:37:29 +00:00
|
|
|
|
/// Repeatedly call sync to synchronize the client state with the server.
|
|
|
|
|
///
|
|
|
|
|
/// This method will never return, if cancellation is needed the method
|
|
|
|
|
/// should be wrapped in a cancelable task or the [`sync_with_callback`]
|
|
|
|
|
/// method can be used.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `sync_settings` - Settings for the sync call. Note that those settings
|
|
|
|
|
/// will be only used for the first sync call.
|
|
|
|
|
///
|
|
|
|
|
/// [`sync_with_callback`]: #method.sync_with_callback
|
|
|
|
|
pub async fn sync(&self, sync_settings: SyncSettings<'_>) {
|
2020-10-06 13:04:43 +00:00
|
|
|
|
self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue })
|
2020-10-06 09:37:29 +00:00
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Repeatedly call sync to synchronize the client state with the server.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `sync_settings` - Settings for the sync call. Note that those settings
|
|
|
|
|
/// will be only used for the first sync call.
|
2020-03-02 10:31:03 +00:00
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// * `callback` - A callback that will be called every time a successful
|
2020-10-06 09:37:29 +00:00
|
|
|
|
/// response has been fetched from the server. The callback must return
|
|
|
|
|
/// a boolean which signalizes if the method should stop syncing. If the
|
2020-10-07 13:26:44 +00:00
|
|
|
|
/// callback returns `LoopCtrl::Continue` the sync will continue, if the
|
|
|
|
|
/// callback returns `LoopCtrl::Break` the sync will be stopped.
|
2020-02-21 13:29:25 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// The following example demonstrates how to sync forever while sending all
|
|
|
|
|
/// the interesting events through a mpsc channel to another thread e.g. a
|
|
|
|
|
/// UI thread.
|
|
|
|
|
///
|
2021-02-10 08:51:14 +00:00
|
|
|
|
/// ```no_run
|
2020-03-02 10:31:03 +00:00
|
|
|
|
/// # use matrix_sdk::events::{
|
|
|
|
|
/// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
|
|
|
|
|
/// # };
|
|
|
|
|
/// # use std::sync::{Arc, RwLock};
|
2020-10-06 13:04:43 +00:00
|
|
|
|
/// # use std::time::Duration;
|
|
|
|
|
/// # use matrix_sdk::{Client, SyncSettings, LoopCtrl};
|
2020-03-02 10:31:03 +00:00
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
2020-10-06 13:04:43 +00:00
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
2020-03-02 10:31:03 +00:00
|
|
|
|
///
|
2021-02-10 08:51:14 +00:00
|
|
|
|
/// use tokio::sync::mpsc::channel;
|
2020-03-02 10:31:03 +00:00
|
|
|
|
///
|
|
|
|
|
/// let (tx, rx) = channel(100);
|
|
|
|
|
///
|
|
|
|
|
/// let sync_channel = &tx;
|
|
|
|
|
/// let sync_settings = SyncSettings::new()
|
2020-10-06 13:04:43 +00:00
|
|
|
|
/// .timeout(Duration::from_secs(30));
|
2020-03-02 10:31:03 +00:00
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// client
|
2021-02-10 08:51:14 +00:00
|
|
|
|
/// .sync_with_callback(sync_settings, |response| async move {
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// let channel = sync_channel;
|
2020-03-02 10:31:03 +00:00
|
|
|
|
///
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// for (room_id, room) in response.rooms.join {
|
|
|
|
|
/// for event in room.timeline.events {
|
2021-02-10 08:51:14 +00:00
|
|
|
|
/// channel.send(event).await.unwrap();
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// }
|
|
|
|
|
/// }
|
2020-10-06 09:37:29 +00:00
|
|
|
|
///
|
2020-10-06 13:04:43 +00:00
|
|
|
|
/// LoopCtrl::Continue
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// })
|
|
|
|
|
/// .await;
|
2020-03-02 10:31:03 +00:00
|
|
|
|
/// })
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// ```
|
2020-03-19 12:55:04 +00:00
|
|
|
|
#[instrument(skip(callback))]
|
2020-10-06 09:37:29 +00:00
|
|
|
|
pub async fn sync_with_callback<C>(
|
2020-04-15 12:29:34 +00:00
|
|
|
|
&self,
|
2020-11-20 18:46:23 +00:00
|
|
|
|
mut sync_settings: SyncSettings<'_>,
|
2020-11-21 21:48:27 +00:00
|
|
|
|
callback: impl Fn(SyncResponse) -> C,
|
2019-12-04 21:33:26 +00:00
|
|
|
|
) where
|
2020-10-06 13:04:43 +00:00
|
|
|
|
C: Future<Output = LoopCtrl>,
|
2019-12-04 21:33:26 +00:00
|
|
|
|
{
|
2020-02-28 09:33:17 +00:00
|
|
|
|
let mut last_sync_time: Option<Instant> = None;
|
2019-12-04 21:33:26 +00:00
|
|
|
|
|
2020-05-22 13:23:58 +00:00
|
|
|
|
if sync_settings.token.is_none() {
|
|
|
|
|
sync_settings.token = self.sync_token().await;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-04 21:33:26 +00:00
|
|
|
|
loop {
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let response = self.sync_once(sync_settings.clone()).await;
|
2019-12-04 21:33:26 +00:00
|
|
|
|
|
2020-07-15 07:43:58 +00:00
|
|
|
|
let response = match response {
|
|
|
|
|
Ok(r) => r,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
error!("Received an invalid response: {}", e);
|
|
|
|
|
sleep::new(Duration::from_secs(1)).await;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-12-04 21:33:26 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-03-11 10:43:58 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
{
|
2021-01-21 13:01:49 +00:00
|
|
|
|
// This is needed because sometimes we need to automatically
|
|
|
|
|
// claim some one-time keys to unwedge an exisitng Olm session.
|
|
|
|
|
if let Err(e) = self.claim_one_time_keys([].iter()).await {
|
2020-10-07 12:07:47 +00:00
|
|
|
|
warn!("Error while claiming one-time keys {:?}", e);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 11:34:25 +00:00
|
|
|
|
for r in self.base_client.outgoing_requests().await {
|
2020-08-21 14:26:34 +00:00
|
|
|
|
match r.request() {
|
2020-08-21 11:34:25 +00:00
|
|
|
|
OutgoingRequests::KeysQuery(request) => {
|
2020-09-12 01:19:22 +00:00
|
|
|
|
if let Err(e) = self
|
|
|
|
|
.keys_query(r.request_id(), request.device_keys.clone())
|
|
|
|
|
.await
|
|
|
|
|
{
|
2020-08-21 11:34:25 +00:00
|
|
|
|
warn!("Error while querying device keys {:?}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
OutgoingRequests::KeysUpload(request) => {
|
2020-08-21 14:26:34 +00:00
|
|
|
|
if let Err(e) = self.keys_upload(&r.request_id(), request).await {
|
2020-08-21 11:34:25 +00:00
|
|
|
|
warn!("Error while querying device keys {:?}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
OutgoingRequests::ToDeviceRequest(request) => {
|
2020-10-01 14:31:24 +00:00
|
|
|
|
// TODO remove this unwrap
|
|
|
|
|
if let Ok(resp) = self.send_to_device(&request).await {
|
2020-08-21 11:34:25 +00:00
|
|
|
|
self.base_client
|
2020-08-21 14:26:34 +00:00
|
|
|
|
.mark_request_as_sent(&r.request_id(), &resp)
|
2020-08-21 11:34:25 +00:00
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-27 15:39:23 +00:00
|
|
|
|
OutgoingRequests::SignatureUpload(request) => {
|
|
|
|
|
// TODO remove this unwrap.
|
2021-02-01 16:15:29 +00:00
|
|
|
|
if let Ok(resp) = self.send(request.clone(), None).await {
|
2020-10-27 15:39:23 +00:00
|
|
|
|
self.base_client
|
|
|
|
|
.mark_request_as_sent(&r.request_id(), &resp)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-15 15:35:54 +00:00
|
|
|
|
OutgoingRequests::RoomMessage(request) => {
|
|
|
|
|
if let Ok(resp) = self.room_send_helper(request).await {
|
|
|
|
|
self.base_client
|
|
|
|
|
.mark_request_as_sent(&r.request_id(), &resp)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-30 13:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-11 10:43:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 13:04:43 +00:00
|
|
|
|
if callback(response).await == LoopCtrl::Break {
|
2020-10-06 09:37:29 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2020-05-12 12:23:57 +00:00
|
|
|
|
|
2020-02-28 09:33:17 +00:00
|
|
|
|
let now = Instant::now();
|
|
|
|
|
|
|
|
|
|
// If the last sync happened less than a second ago, sleep for a
|
|
|
|
|
// while to not hammer out requests if the server doesn't respect
|
|
|
|
|
// the sync timeout.
|
2020-05-24 14:05:02 +00:00
|
|
|
|
if let Some(t) = last_sync_time {
|
|
|
|
|
if now - t <= Duration::from_secs(1) {
|
|
|
|
|
sleep::new(Duration::from_secs(1)).await;
|
2020-02-28 09:33:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last_sync_time = Some(now);
|
|
|
|
|
|
2021-04-01 17:25:31 +00:00
|
|
|
|
sync_settings.token = Some(
|
2020-03-24 15:18:56 +00:00
|
|
|
|
self.sync_token()
|
|
|
|
|
.await
|
|
|
|
|
.expect("No sync token found after initial sync"),
|
|
|
|
|
);
|
2019-12-04 21:33:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-26 19:34:11 +00:00
|
|
|
|
|
2020-04-03 08:27:30 +00:00
|
|
|
|
/// Claim one-time keys creating new Olm sessions.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `users` - The list of user/device pairs that we should claim keys for.
|
|
|
|
|
///
|
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-10-07 12:07:47 +00:00
|
|
|
|
#[instrument(skip(users))]
|
2021-03-08 23:12:59 +00:00
|
|
|
|
pub(crate) async fn claim_one_time_keys(
|
|
|
|
|
&self,
|
|
|
|
|
users: impl Iterator<Item = &UserId>,
|
|
|
|
|
) -> Result<()> {
|
2020-10-07 12:07:47 +00:00
|
|
|
|
let _lock = self.key_claim_lock.lock().await;
|
|
|
|
|
|
|
|
|
|
if let Some((request_id, request)) = self.base_client.get_missing_sessions(users).await? {
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-10-07 12:07:47 +00:00
|
|
|
|
self.base_client
|
|
|
|
|
.mark_request_as_sent(&request_id, &response)
|
|
|
|
|
.await?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2020-04-03 08:27:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-11 10:42:59 +00:00
|
|
|
|
/// Upload the E2E encryption keys.
|
|
|
|
|
///
|
|
|
|
|
/// This uploads the long lived device keys as well as the required amount
|
|
|
|
|
/// of one-time keys.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// Panics if the client isn't logged in, or if no encryption keys need to
|
|
|
|
|
/// be uploaded.
|
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-03-19 12:55:04 +00:00
|
|
|
|
#[instrument]
|
2020-08-21 11:34:25 +00:00
|
|
|
|
async fn keys_upload(
|
|
|
|
|
&self,
|
|
|
|
|
request_id: &Uuid,
|
2020-08-21 14:26:34 +00:00
|
|
|
|
request: &upload_keys::Request,
|
2020-08-21 11:34:25 +00:00
|
|
|
|
) -> Result<upload_keys::Response> {
|
2020-03-19 12:55:04 +00:00
|
|
|
|
debug!(
|
|
|
|
|
"Uploading encryption keys device keys: {}, one-time-keys: {}",
|
2020-08-12 13:12:51 +00:00
|
|
|
|
request.device_keys.is_some(),
|
|
|
|
|
request.one_time_keys.as_ref().map_or(0, |k| k.len())
|
2020-03-19 12:55:04 +00:00
|
|
|
|
);
|
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request.clone(), None).await?;
|
2020-03-11 10:42:59 +00:00
|
|
|
|
self.base_client
|
2020-08-21 11:34:25 +00:00
|
|
|
|
.mark_request_as_sent(request_id, &response)
|
2020-03-18 14:50:32 +00:00
|
|
|
|
.await?;
|
2020-08-21 11:34:25 +00:00
|
|
|
|
|
2020-03-11 10:42:59 +00:00
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:29:25 +00:00
|
|
|
|
/// Get the current, if any, sync token of the client.
|
|
|
|
|
/// This will be None if the client didn't sync at least once.
|
2020-03-11 10:42:59 +00:00
|
|
|
|
pub async fn sync_token(&self) -> Option<String> {
|
2020-05-06 13:36:55 +00:00
|
|
|
|
self.base_client.sync_token().await
|
2019-11-24 21:40:52 +00:00
|
|
|
|
}
|
2020-04-01 13:37:00 +00:00
|
|
|
|
|
|
|
|
|
/// Query the server for users device keys.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// Panics if no key query needs to be done.
|
2020-04-03 08:42:03 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-04-03 08:42:03 +00:00
|
|
|
|
#[instrument]
|
2020-08-21 11:34:25 +00:00
|
|
|
|
async fn keys_query(
|
|
|
|
|
&self,
|
|
|
|
|
request_id: &Uuid,
|
2020-09-12 01:19:22 +00:00
|
|
|
|
device_keys: BTreeMap<UserId, Vec<DeviceIdBox>>,
|
2020-08-21 11:34:25 +00:00
|
|
|
|
) -> Result<get_keys::Response> {
|
2020-09-12 01:19:22 +00:00
|
|
|
|
let request = assign!(get_keys::Request::new(), { device_keys });
|
2020-04-01 13:37:00 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
let response = self.send(request, None).await?;
|
2020-04-01 13:37:00 +00:00
|
|
|
|
self.base_client
|
2020-08-21 11:34:25 +00:00
|
|
|
|
.mark_request_as_sent(request_id, &response)
|
2020-04-01 13:37:00 +00:00
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
2020-07-29 12:19:47 +00:00
|
|
|
|
|
2020-08-04 09:41:20 +00:00
|
|
|
|
/// Get a `Sas` verification object with the given flow id.
|
2020-07-29 12:19:47 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-07-29 12:19:47 +00:00
|
|
|
|
pub async fn get_verification(&self, flow_id: &str) -> Option<Sas> {
|
|
|
|
|
self.base_client
|
|
|
|
|
.get_verification(flow_id)
|
|
|
|
|
.await
|
|
|
|
|
.map(|sas| Sas {
|
|
|
|
|
inner: sas,
|
2020-12-15 15:35:54 +00:00
|
|
|
|
client: self.clone(),
|
2020-07-29 12:19:47 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
2020-08-12 09:39:47 +00:00
|
|
|
|
|
2020-12-09 16:18:23 +00:00
|
|
|
|
/// Get a `VerificationRequest` object with the given flow id.
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
|
|
|
|
pub async fn get_verification_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
|
|
|
|
|
let olm = self.base_client.olm_machine().await?;
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
olm.get_verification_request(flow_id).and_then(|r| {
|
|
|
|
|
if let Some(room) = self.get_joined_room(r.room_id()) {
|
|
|
|
|
Some(VerificationRequest { inner: r, room })
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
2020-07-29 12:19:47 +00:00
|
|
|
|
}
|
2020-08-12 09:39:47 +00:00
|
|
|
|
|
2020-08-13 08:49:38 +00:00
|
|
|
|
/// Get a specific device of a user.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `user_id` - The unique id of the user that the device belongs to.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - The unique id of the device.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a `Device` if one is found and the crypto store didn't throw an
|
|
|
|
|
/// error.
|
|
|
|
|
///
|
|
|
|
|
/// This will always return None if the client hasn't been logged in.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
2020-08-17 13:54:54 +00:00
|
|
|
|
/// ```no_run
|
2020-08-13 08:49:38 +00:00
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # use matrix_sdk::{Client, identifiers::UserId};
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// # block_on(async {
|
2020-09-29 12:18:03 +00:00
|
|
|
|
/// let device = client.get_device(&alice, "DEVICEID".into())
|
|
|
|
|
/// .await
|
|
|
|
|
/// .unwrap()
|
|
|
|
|
/// .unwrap();
|
2020-08-17 13:54:54 +00:00
|
|
|
|
///
|
|
|
|
|
/// println!("{:?}", device.is_trusted());
|
|
|
|
|
///
|
|
|
|
|
/// let verification = device.start_verification().await.unwrap();
|
2020-08-13 08:49:38 +00:00
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-09-28 07:27:16 +00:00
|
|
|
|
pub async fn get_device(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
device_id: &DeviceId,
|
|
|
|
|
) -> StdResult<Option<Device>, CryptoStoreError> {
|
2020-08-17 13:34:05 +00:00
|
|
|
|
let device = self.base_client.get_device(user_id, device_id).await?;
|
|
|
|
|
|
2020-09-28 07:27:16 +00:00
|
|
|
|
Ok(device.map(|d| Device {
|
|
|
|
|
inner: d,
|
2020-12-15 15:35:54 +00:00
|
|
|
|
client: self.clone(),
|
2020-09-28 07:27:16 +00:00
|
|
|
|
}))
|
2020-08-13 08:49:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 13:33:45 +00:00
|
|
|
|
/// Create and upload a new cross signing identity.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `auth_data` - This request requires user interactive auth, the first
|
|
|
|
|
/// request needs to set this to `None` and will always fail with an
|
|
|
|
|
/// `UiaaResponse`. The response will contain information for the
|
|
|
|
|
/// interactive auth and the same request needs to be made but this time
|
|
|
|
|
/// with some `auth_data` provided.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
|
|
|
|
/// # use matrix_sdk::{Client, identifiers::UserId};
|
|
|
|
|
/// # use matrix_sdk::api::r0::uiaa::AuthData;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use serde_json::json;
|
|
|
|
|
/// # let user_id = UserId::try_from("@alice:example.org").unwrap();
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
///
|
|
|
|
|
/// fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> {
|
|
|
|
|
/// let mut auth_parameters = BTreeMap::new();
|
|
|
|
|
/// let identifier = json!({
|
|
|
|
|
/// "type": "m.id.user",
|
|
|
|
|
/// "user": user,
|
|
|
|
|
/// });
|
|
|
|
|
/// auth_parameters.insert("identifier".to_owned(), identifier);
|
|
|
|
|
/// auth_parameters.insert("password".to_owned(), password.to_owned().into());
|
|
|
|
|
/// // This is needed because of https://github.com/matrix-org/synapse/issues/5665
|
|
|
|
|
/// auth_parameters.insert("user".to_owned(), user.as_str().into());
|
|
|
|
|
/// AuthData::DirectRequest {
|
|
|
|
|
/// kind: "m.login.password",
|
|
|
|
|
/// auth_parameters,
|
|
|
|
|
/// session,
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// if let Err(e) = client.bootstrap_cross_signing(None).await {
|
|
|
|
|
/// if let Some(response) = e.uiaa_response() {
|
|
|
|
|
/// let auth_data = auth_data(&user_id, "wordpass", response.session.as_deref());
|
|
|
|
|
/// client
|
|
|
|
|
/// .bootstrap_cross_signing(Some(auth_data))
|
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Couldn't bootstrap cross signing")
|
|
|
|
|
/// } else {
|
|
|
|
|
/// panic!("Error durign cross signing bootstrap {:#?}", e);
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
/// # })
|
2020-10-24 08:32:17 +00:00
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
|
|
|
|
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
|
|
|
|
let olm = self
|
|
|
|
|
.base_client
|
|
|
|
|
.olm_machine()
|
|
|
|
|
.await
|
|
|
|
|
.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
|
|
|
|
|
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
|
|
|
|
|
2020-11-26 12:24:57 +00:00
|
|
|
|
let request = assign!(UploadSigningKeysRequest::new(), {
|
2020-10-24 08:32:17 +00:00
|
|
|
|
auth: auth_data,
|
|
|
|
|
master_key: request.master_key,
|
|
|
|
|
self_signing_key: request.self_signing_key,
|
|
|
|
|
user_signing_key: request.user_signing_key,
|
2020-11-26 12:24:57 +00:00
|
|
|
|
});
|
2020-10-24 08:32:17 +00:00
|
|
|
|
|
2021-02-01 16:15:29 +00:00
|
|
|
|
self.send(request, None).await?;
|
|
|
|
|
self.send(signature_request, None).await?;
|
2020-10-24 08:32:17 +00:00
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-13 08:49:38 +00:00
|
|
|
|
/// Get a map holding all the devices of an user.
|
|
|
|
|
///
|
|
|
|
|
/// This will always return an empty map if the client hasn't been logged
|
|
|
|
|
/// in.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `user_id` - The unique id of the user that the devices belong to.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
2020-08-17 13:34:05 +00:00
|
|
|
|
/// ```no_run
|
2020-08-13 08:49:38 +00:00
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # use matrix_sdk::{Client, identifiers::UserId};
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
|
|
|
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
|
|
|
|
/// # let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let devices = client.get_user_devices(&alice).await.unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// for device in devices.devices() {
|
|
|
|
|
/// println!("{:?}", device);
|
|
|
|
|
/// }
|
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
#[cfg(feature = "encryption")]
|
2020-08-13 10:18:24 +00:00
|
|
|
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
2020-08-13 08:49:38 +00:00
|
|
|
|
pub async fn get_user_devices(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
) -> StdResult<UserDevices, CryptoStoreError> {
|
2020-08-17 13:34:05 +00:00
|
|
|
|
let devices = self.base_client.get_user_devices(user_id).await?;
|
|
|
|
|
|
|
|
|
|
Ok(UserDevices {
|
|
|
|
|
inner: devices,
|
2020-12-15 15:35:54 +00:00
|
|
|
|
client: self.clone(),
|
2020-08-17 13:34:05 +00:00
|
|
|
|
})
|
2020-08-13 08:49:38 +00:00
|
|
|
|
}
|
2020-09-11 14:34:39 +00:00
|
|
|
|
|
|
|
|
|
/// Export E2EE keys that match the given predicate encrypting them with the
|
|
|
|
|
/// given passphrase.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `path` - The file path where the exported key file will be saved.
|
|
|
|
|
///
|
|
|
|
|
/// * `passphrase` - The passphrase that will be used to encrypt the exported
|
|
|
|
|
/// room keys.
|
|
|
|
|
///
|
|
|
|
|
/// * `predicate` - A closure that will be called for every known
|
|
|
|
|
/// `InboundGroupSession`, which represents a room key. If the closure
|
|
|
|
|
/// returns `true` the `InboundGroupSessoin` will be included in the export,
|
|
|
|
|
/// if the closure returns `false` it will not be included.
|
|
|
|
|
///
|
2020-09-18 12:04:39 +00:00
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
2020-10-17 12:39:19 +00:00
|
|
|
|
/// This method will panic if it isn't run on a Tokio runtime.
|
|
|
|
|
///
|
2020-09-18 12:04:39 +00:00
|
|
|
|
/// This method will panic if it can't get enough randomness from the OS to
|
|
|
|
|
/// encrypt the exported keys securely.
|
|
|
|
|
///
|
2020-09-11 14:34:39 +00:00
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::{path::PathBuf, time::Duration};
|
|
|
|
|
/// # use matrix_sdk::{
|
|
|
|
|
/// # Client, SyncSettings,
|
|
|
|
|
/// # identifiers::room_id,
|
|
|
|
|
/// # };
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
|
|
|
|
/// // Export all room keys.
|
|
|
|
|
/// client
|
|
|
|
|
/// .export_keys(path, "secret-passphrase", |_| true)
|
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Can't export keys.");
|
|
|
|
|
///
|
|
|
|
|
/// // Export only the room keys for a certain room.
|
|
|
|
|
/// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
|
|
|
|
|
/// let room_id = room_id!("!test:localhost");
|
|
|
|
|
///
|
|
|
|
|
/// client
|
|
|
|
|
/// .export_keys(path, "secret-passphrase", |s| s.room_id() == &room_id)
|
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Can't export keys.");
|
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2020-10-06 09:41:18 +00:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "docs",
|
|
|
|
|
doc(cfg(all(encryption, not(target_arch = "wasm32"))))
|
|
|
|
|
)]
|
2020-09-11 14:34:39 +00:00
|
|
|
|
pub async fn export_keys(
|
|
|
|
|
&self,
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
passphrase: &str,
|
|
|
|
|
predicate: impl FnMut(&InboundGroupSession) -> bool,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let olm = self
|
|
|
|
|
.base_client
|
|
|
|
|
.olm_machine()
|
|
|
|
|
.await
|
|
|
|
|
.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
|
|
|
|
|
let keys = olm.export_keys(predicate).await?;
|
|
|
|
|
let passphrase = Zeroizing::new(passphrase.to_owned());
|
|
|
|
|
|
|
|
|
|
let encrypt = move || -> Result<()> {
|
|
|
|
|
let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?;
|
2020-12-18 17:23:42 +00:00
|
|
|
|
let mut file = std::fs::File::create(path)?;
|
|
|
|
|
file.write_all(&export.into_bytes())?;
|
2020-09-11 14:34:39 +00:00
|
|
|
|
Ok(())
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let task = tokio::task::spawn_blocking(encrypt);
|
|
|
|
|
task.await.expect("Task join error")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Import E2EE keys from the given file path.
|
|
|
|
|
///
|
2020-10-17 12:39:19 +00:00
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `path` - The file path where the exported key file will can be found.
|
|
|
|
|
///
|
|
|
|
|
/// * `passphrase` - The passphrase that should be used to decrypt the
|
|
|
|
|
/// exported room keys.
|
|
|
|
|
///
|
2020-12-02 10:12:46 +00:00
|
|
|
|
/// Returns a tuple of numbers that represent the number of sessions that
|
|
|
|
|
/// were imported and the total number of sessions that were found in the
|
|
|
|
|
/// key export.
|
|
|
|
|
///
|
2020-10-17 12:39:19 +00:00
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// This method will panic if it isn't run on a Tokio runtime.
|
|
|
|
|
///
|
2020-09-11 14:34:39 +00:00
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::{path::PathBuf, time::Duration};
|
|
|
|
|
/// # use matrix_sdk::{
|
|
|
|
|
/// # Client, SyncSettings,
|
|
|
|
|
/// # identifiers::room_id,
|
|
|
|
|
/// # };
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
|
|
|
|
/// # let mut client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let path = PathBuf::from("/home/example/e2e-keys.txt");
|
|
|
|
|
/// client
|
|
|
|
|
/// .import_keys(path, "secret-passphrase")
|
|
|
|
|
/// .await
|
|
|
|
|
/// .expect("Can't import keys");
|
|
|
|
|
/// # });
|
|
|
|
|
/// ```
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2020-10-06 09:41:18 +00:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "docs",
|
|
|
|
|
doc(cfg(all(encryption, not(target_arch = "wasm32"))))
|
|
|
|
|
)]
|
2020-12-02 10:12:46 +00:00
|
|
|
|
pub async fn import_keys(&self, path: PathBuf, passphrase: &str) -> Result<(usize, usize)> {
|
2020-09-11 14:34:39 +00:00
|
|
|
|
let olm = self
|
|
|
|
|
.base_client
|
|
|
|
|
.olm_machine()
|
|
|
|
|
.await
|
|
|
|
|
.ok_or(Error::AuthenticationRequired)?;
|
|
|
|
|
let passphrase = Zeroizing::new(passphrase.to_owned());
|
|
|
|
|
|
|
|
|
|
let decrypt = move || {
|
|
|
|
|
let file = std::fs::File::open(path)?;
|
|
|
|
|
decrypt_key_export(file, &passphrase)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let task = tokio::task::spawn_blocking(decrypt);
|
2020-12-18 17:23:42 +00:00
|
|
|
|
// TODO remove this unwrap.
|
2020-09-11 14:34:39 +00:00
|
|
|
|
let import = task.await.expect("Task join error").unwrap();
|
|
|
|
|
|
2020-10-17 12:39:19 +00:00
|
|
|
|
Ok(olm.import_keys(import).await?)
|
2020-09-11 14:34:39 +00:00
|
|
|
|
}
|
2019-10-23 20:47:00 +00:00
|
|
|
|
}
|
2020-04-07 00:59:44 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
2021-04-01 17:25:31 +00:00
|
|
|
|
use crate::{ClientConfig, HttpError, RequestConfig, RoomMember};
|
2021-01-27 13:43:53 +00:00
|
|
|
|
|
2020-05-06 12:36:28 +00:00
|
|
|
|
use super::{
|
2021-03-08 23:12:59 +00:00
|
|
|
|
get_public_rooms, get_public_rooms_filtered, register::RegistrationKind, Client, Session,
|
|
|
|
|
SyncSettings, Url,
|
2020-05-06 12:36:28 +00:00
|
|
|
|
};
|
2021-04-06 10:34:58 +00:00
|
|
|
|
use matrix_sdk_base::identifiers::mxc_uri;
|
2020-08-10 10:39:00 +00:00
|
|
|
|
use matrix_sdk_common::{
|
2020-08-26 11:40:38 +00:00
|
|
|
|
api::r0::{
|
|
|
|
|
account::register::Request as RegistrationRequest,
|
|
|
|
|
directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest,
|
2021-03-23 13:27:55 +00:00
|
|
|
|
membership::Invite3pid, session::get_login_types::LoginType, uiaa::AuthData,
|
2020-08-26 11:40:38 +00:00
|
|
|
|
},
|
|
|
|
|
assign,
|
2020-08-15 01:09:13 +00:00
|
|
|
|
directory::Filter,
|
2020-11-25 17:50:50 +00:00
|
|
|
|
events::{room::message::MessageEventContent, AnyMessageEventContent},
|
2020-08-04 22:56:26 +00:00
|
|
|
|
identifiers::{event_id, room_id, user_id},
|
2020-08-10 10:39:00 +00:00
|
|
|
|
thirdparty,
|
2020-07-30 23:22:48 +00:00
|
|
|
|
};
|
2020-06-22 19:40:51 +00:00
|
|
|
|
use matrix_sdk_test::{test_json, EventBuilder, EventsJson};
|
2020-05-06 12:36:28 +00:00
|
|
|
|
use mockito::{mock, Matcher};
|
2020-09-15 16:06:32 +00:00
|
|
|
|
use serde_json::json;
|
2020-05-18 20:26:27 +00:00
|
|
|
|
|
2020-12-15 09:23:31 +00:00
|
|
|
|
use std::{collections::BTreeMap, convert::TryInto, io::Cursor, str::FromStr, time::Duration};
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
async fn logged_in_client() -> Client {
|
|
|
|
|
let session = Session {
|
|
|
|
|
access_token: "1234".to_owned(),
|
|
|
|
|
user_id: user_id!("@example:localhost"),
|
|
|
|
|
device_id: "DEVICEID".into(),
|
|
|
|
|
};
|
|
|
|
|
let homeserver = url::Url::parse(&mockito::server_url()).unwrap();
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
client.restore_login(session).await.unwrap();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
|
2021-03-23 13:27:55 +00:00
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
let _m_types = mock("GET", "/_matrix/client/r0/login")
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.with_status(200)
|
2021-03-23 13:27:55 +00:00
|
|
|
|
.with_body(test_json::LOGIN_TYPES.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-23 13:27:55 +00:00
|
|
|
|
let can_password = client
|
|
|
|
|
.get_login_types()
|
|
|
|
|
.await
|
|
|
|
|
.unwrap()
|
|
|
|
|
.flows
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|flow| flow == &LoginType::Password);
|
|
|
|
|
assert!(can_password);
|
|
|
|
|
|
|
|
|
|
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
2020-08-12 13:53:42 +00:00
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let logged_in = client.logged_in().await;
|
|
|
|
|
assert!(logged_in, "Client should be logged in");
|
2021-03-23 14:30:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login_with_sso() {
|
|
|
|
|
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-23 14:30:40 +00:00
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
client
|
2021-03-23 14:30:40 +00:00
|
|
|
|
.login_with_sso(
|
|
|
|
|
|sso_url| async move {
|
|
|
|
|
let sso_url = Url::parse(sso_url.as_str()).unwrap();
|
|
|
|
|
|
|
|
|
|
let (_, redirect) = sso_url
|
|
|
|
|
.query_pairs()
|
|
|
|
|
.find(|(key, _)| key == "redirectUrl")
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let mut redirect_url = Url::parse(redirect.into_owned().as_str()).unwrap();
|
|
|
|
|
redirect_url.set_query(Some("loginToken=tinytoken"));
|
|
|
|
|
|
|
|
|
|
reqwest::get(redirect_url.to_string()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
},
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let logged_in = client.logged_in().await;
|
|
|
|
|
assert!(logged_in, "Client should be logged in");
|
2020-08-12 13:53:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:47:15 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login_with_sso_token() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
let _m = mock("GET", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN_TYPES.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let can_sso = client
|
|
|
|
|
.get_login_types()
|
|
|
|
|
.await
|
|
|
|
|
.unwrap()
|
|
|
|
|
.flows
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|flow| flow == &LoginType::Sso);
|
|
|
|
|
assert!(can_sso);
|
|
|
|
|
|
|
|
|
|
let sso_url = client.get_sso_login_url("http://127.0.0.1:3030");
|
|
|
|
|
assert!(sso_url.is_ok());
|
2021-03-23 14:17:12 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.login_with_token("averysmalltoken", None, None)
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let logged_in = client.logged_in().await;
|
|
|
|
|
assert!(logged_in, "Client should be logged in");
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-05 16:04:15 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn devices() {
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock("GET", "/_matrix/client/r0/devices")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::DEVICES.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
assert!(client.devices().await.is_ok());
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-22 09:47:21 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_join_leave_room() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
|
|
|
|
|
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
|
|
|
|
|
|
|
|
|
let session = Session {
|
|
|
|
|
access_token: "1234".to_owned(),
|
|
|
|
|
user_id: user_id!("@example:localhost"),
|
|
|
|
|
device_id: "DEVICEID".into(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let client = Client::new(homeserver.clone()).unwrap();
|
|
|
|
|
client.restore_login(session.clone()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client.get_joined_room(&room_id);
|
|
|
|
|
assert!(room.is_none());
|
|
|
|
|
|
|
|
|
|
client.sync_once(SyncSettings::default()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client.get_left_room(&room_id);
|
|
|
|
|
assert!(room.is_none());
|
|
|
|
|
|
|
|
|
|
let room = client.get_joined_room(&room_id);
|
|
|
|
|
assert!(room.is_some());
|
|
|
|
|
|
2021-01-27 13:43:53 +00:00
|
|
|
|
// test store reloads with correct room state from the sled store
|
|
|
|
|
let path = tempfile::tempdir().unwrap();
|
|
|
|
|
let config = ClientConfig::default().store_path(path);
|
|
|
|
|
let joined_client = Client::new_with_config(homeserver, config).unwrap();
|
|
|
|
|
joined_client.restore_login(session).await.unwrap();
|
|
|
|
|
|
|
|
|
|
// joined room reloaded from state store
|
|
|
|
|
joined_client
|
|
|
|
|
.sync_once(SyncSettings::default())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
let room = joined_client.get_joined_room(&room_id);
|
|
|
|
|
assert!(room.is_some());
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LEAVE_SYNC_EVENT.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
joined_client
|
|
|
|
|
.sync_once(SyncSettings::default())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = joined_client.get_joined_room(&room_id);
|
|
|
|
|
assert!(room.is_none());
|
|
|
|
|
|
|
|
|
|
let room = joined_client.get_left_room(&room_id);
|
|
|
|
|
assert!(room.is_some());
|
2020-12-22 09:47:21 +00:00
|
|
|
|
}
|
2020-05-18 20:26:27 +00:00
|
|
|
|
|
2020-05-07 10:51:53 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn account_data() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-07 10:51:53 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
2020-05-07 14:22:18 +00:00
|
|
|
|
// let bc = &client.base_client;
|
|
|
|
|
// let ignored_users = bc.ignored_users.read().await;
|
|
|
|
|
// assert_eq!(1, ignored_users.len())
|
2020-05-07 10:51:53 +00:00
|
|
|
|
}
|
2020-04-07 00:59:44 +00:00
|
|
|
|
|
2020-05-08 07:57:42 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_creation() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-08 07:57:42 +00:00
|
|
|
|
|
2020-12-15 09:23:31 +00:00
|
|
|
|
let response = EventBuilder::default()
|
2020-06-25 00:30:53 +00:00
|
|
|
|
.add_state_event(EventsJson::Member)
|
|
|
|
|
.add_state_event(EventsJson::PowerLevels)
|
2020-05-08 07:57:42 +00:00
|
|
|
|
.build_sync_response();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.base_client
|
2020-12-15 09:23:31 +00:00
|
|
|
|
.receive_sync_response(response)
|
2020-05-08 07:57:42 +00:00
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
2020-08-04 22:56:26 +00:00
|
|
|
|
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
2020-05-08 07:57:42 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
client.homeserver(),
|
|
|
|
|
&Url::parse(&mockito::server_url()).unwrap()
|
|
|
|
|
);
|
|
|
|
|
|
2020-12-15 09:23:31 +00:00
|
|
|
|
let room = client.get_joined_room(&room_id);
|
2020-05-08 07:57:42 +00:00
|
|
|
|
assert!(room.is_some());
|
|
|
|
|
}
|
2020-04-16 21:03:40 +00:00
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login_error() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
2020-04-16 21:03:40 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(403)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGIN_RESPONSE_ERR.to_string())
|
2020-04-16 21:03:40 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
if let Err(err) = client.login("example", "wordpass", None, None).await {
|
2021-01-31 17:09:03 +00:00
|
|
|
|
if let crate::Error::Http(HttpError::FromHttpResponse(
|
|
|
|
|
crate::FromHttpResponseError::Http(crate::ServerError::Known(crate::api::Error {
|
2020-04-16 21:03:40 +00:00
|
|
|
|
kind,
|
|
|
|
|
message,
|
|
|
|
|
status_code,
|
2021-01-31 17:09:03 +00:00
|
|
|
|
})),
|
2020-04-16 21:03:40 +00:00
|
|
|
|
)) = err
|
|
|
|
|
{
|
2020-04-29 08:40:27 +00:00
|
|
|
|
if let crate::api::error::ErrorKind::Forbidden = kind {
|
2020-04-16 21:03:40 +00:00
|
|
|
|
} else {
|
|
|
|
|
panic!(
|
|
|
|
|
"found the wrong `ErrorKind` {:?}, expected `Forbidden",
|
|
|
|
|
kind
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(message, "Invalid password".to_string());
|
|
|
|
|
assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap());
|
|
|
|
|
} else {
|
|
|
|
|
panic!(
|
|
|
|
|
"found the wrong `Error` type {:?}, expected `Error::RumaResponse",
|
|
|
|
|
err
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
panic!("this request should return an `Err` variant")
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2020-06-02 18:57:56 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn register_error() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
2020-06-02 18:57:56 +00:00
|
|
|
|
|
2021-04-02 10:13:56 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(403)
|
|
|
|
|
.with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string())
|
|
|
|
|
.create();
|
2020-06-02 18:57:56 +00:00
|
|
|
|
|
2020-08-26 11:40:38 +00:00
|
|
|
|
let user = assign!(RegistrationRequest::new(), {
|
|
|
|
|
username: Some("user"),
|
|
|
|
|
password: Some("password"),
|
|
|
|
|
auth: Some(AuthData::FallbackAcknowledgement { session: "foobar" }),
|
|
|
|
|
kind: RegistrationKind::User,
|
|
|
|
|
});
|
2020-06-02 18:57:56 +00:00
|
|
|
|
|
2020-08-26 11:40:38 +00:00
|
|
|
|
if let Err(err) = client.register(user).await {
|
2021-01-31 17:09:03 +00:00
|
|
|
|
if let crate::Error::Http(HttpError::UiaaError(crate::FromHttpResponseError::Http(
|
2021-04-02 10:13:56 +00:00
|
|
|
|
crate::ServerError::Known(crate::api::r0::uiaa::UiaaResponse::MatrixError(
|
|
|
|
|
crate::api::Error {
|
|
|
|
|
kind,
|
|
|
|
|
message,
|
|
|
|
|
status_code,
|
|
|
|
|
},
|
|
|
|
|
)),
|
2021-01-31 17:09:03 +00:00
|
|
|
|
))) = err
|
2020-06-02 18:57:56 +00:00
|
|
|
|
{
|
2021-04-02 10:13:56 +00:00
|
|
|
|
if let crate::api::error::ErrorKind::Forbidden = kind {
|
|
|
|
|
} else {
|
|
|
|
|
panic!(
|
|
|
|
|
"found the wrong `ErrorKind` {:?}, expected `Forbidden",
|
|
|
|
|
kind
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(message, "Invalid password".to_string());
|
|
|
|
|
assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap());
|
2020-06-02 18:57:56 +00:00
|
|
|
|
} else {
|
|
|
|
|
panic!(
|
2021-04-02 10:13:56 +00:00
|
|
|
|
"found the wrong `Error` type {:#?}, expected `UiaaResponse`",
|
2020-06-02 18:57:56 +00:00
|
|
|
|
err
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
panic!("this request should return an `Err` variant")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 12:36:28 +00:00
|
|
|
|
#[tokio::test]
|
2020-05-07 19:21:06 +00:00
|
|
|
|
async fn join_room_by_id() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::ROOM_ID.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2020-08-04 22:56:26 +00:00
|
|
|
|
let room_id = room_id!("!testroom:example.org");
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
// this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId field
|
|
|
|
|
client.join_room_by_id(&room_id).await.unwrap().room_id,
|
|
|
|
|
room_id
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 10:32:36 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn join_room_by_id_or_alias() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-15 10:32:36 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/join/".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::ROOM_ID.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-15 10:32:36 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2020-08-04 22:56:26 +00:00
|
|
|
|
let room_id = room_id!("!testroom:example.org").into();
|
2020-05-15 10:32:36 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
// this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId field
|
|
|
|
|
client
|
2020-07-22 18:43:47 +00:00
|
|
|
|
.join_room_by_id_or_alias(&room_id, &["server.com".try_into().unwrap()])
|
2020-05-15 10:32:36 +00:00
|
|
|
|
.await
|
|
|
|
|
.unwrap()
|
|
|
|
|
.room_id,
|
2020-08-04 22:56:26 +00:00
|
|
|
|
room_id!("!testroom:example.org")
|
2020-05-15 10:32:36 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2020-05-06 12:36:28 +00:00
|
|
|
|
#[tokio::test]
|
2020-05-07 19:21:06 +00:00
|
|
|
|
async fn invite_user_by_id() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let user = user_id!("@example:localhost");
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room.invite_user_by_id(&user).await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 19:21:06 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn invite_user_by_3pid() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
// empty JSON object
|
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-07 19:21:06 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
2020-08-15 00:49:34 +00:00
|
|
|
|
.unwrap();
|
2021-03-08 23:12:59 +00:00
|
|
|
|
|
|
|
|
|
room.invite_user_by_3pid(Invite3pid {
|
|
|
|
|
id_server: "example.org",
|
|
|
|
|
id_access_token: "IdToken",
|
|
|
|
|
medium: thirdparty::Medium::Email,
|
|
|
|
|
address: "address",
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
2020-05-07 19:21:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-23 21:31:58 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_search_all() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
2020-06-23 21:31:58 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-25 12:31:51 +00:00
|
|
|
|
.with_body(test_json::PUBLIC_ROOMS.to_string())
|
2020-06-23 21:31:58 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2020-08-15 00:49:34 +00:00
|
|
|
|
let get_public_rooms::Response { chunk, .. } =
|
|
|
|
|
client.public_rooms(Some(10), None, None).await.unwrap();
|
|
|
|
|
assert_eq!(chunk.len(), 1);
|
2020-06-23 21:31:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_search_filtered() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-06-23 21:31:58 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-25 12:31:51 +00:00
|
|
|
|
.with_body(test_json::PUBLIC_ROOMS.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-06-23 21:31:58 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2020-08-26 11:40:38 +00:00
|
|
|
|
let generic_search_term = Some("cheese");
|
2020-10-18 00:01:39 +00:00
|
|
|
|
let filter = assign!(Filter::new(), { generic_search_term });
|
2020-08-26 11:40:38 +00:00
|
|
|
|
let request = assign!(PublicRoomsFilterRequest::new(), { filter });
|
2020-06-23 21:31:58 +00:00
|
|
|
|
|
2020-08-15 00:49:34 +00:00
|
|
|
|
let get_public_rooms_filtered::Response { chunk, .. } =
|
|
|
|
|
client.public_rooms_filtered(request).await.unwrap();
|
|
|
|
|
assert_eq!(chunk.len(), 1);
|
2020-06-23 21:31:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 12:36:28 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn leave_room() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room.leave().await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn ban_user() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let user = user_id!("@example:localhost");
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
room.ban_user(&user, None).await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn kick_user() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let user = user_id!("@example:localhost");
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room.kick_user(&user, None).await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn forget_room() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::LEAVE_SYNC.to_string())
|
|
|
|
|
.create();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_left_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
room.forget().await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn read_receipt() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let event_id = event_id!("$xxxxxx:example.org");
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room.read_receipt(&event_id).await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 10:38:55 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn read_marker() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-23 10:38:55 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-23 10:38:55 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let event_id = event_id!("$xxxxxx:example.org");
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-23 10:38:55 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
room.read_marker(&event_id, None).await.unwrap();
|
2020-05-23 10:38:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 12:36:28 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn typing_notice() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"PUT",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
// this is an empty JSON object
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-06 12:36:28 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2021-03-12 19:18:43 +00:00
|
|
|
|
room.typing_notice(true).await.unwrap();
|
2020-05-06 12:36:28 +00:00
|
|
|
|
}
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
2021-03-13 08:16:48 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_state_event_send() {
|
|
|
|
|
use crate::events::{
|
|
|
|
|
room::member::{MemberEventContent, MembershipState},
|
|
|
|
|
AnyStateEventContent,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"PUT",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::EVENT_ID.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
|
|
|
|
|
|
|
|
|
let room = client.get_joined_room(&room_id).unwrap();
|
2021-03-13 08:16:48 +00:00
|
|
|
|
|
2021-04-05 17:49:55 +00:00
|
|
|
|
let avatar_url = mxc_uri!("mxc://example.org/avA7ar");
|
2021-03-13 08:16:48 +00:00
|
|
|
|
let member_event = MemberEventContent {
|
2021-04-05 17:49:55 +00:00
|
|
|
|
avatar_url: Some(avatar_url),
|
2021-03-13 08:16:48 +00:00
|
|
|
|
membership: MembershipState::Join,
|
|
|
|
|
is_direct: None,
|
|
|
|
|
displayname: None,
|
|
|
|
|
third_party_invite: None,
|
|
|
|
|
};
|
|
|
|
|
let content = AnyStateEventContent::RoomMember(member_event);
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let response = room.send_state_event(content, "").await.unwrap();
|
2021-03-13 08:16:48 +00:00
|
|
|
|
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 19:21:06 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_message_send() {
|
2020-05-08 14:12:21 +00:00
|
|
|
|
use matrix_sdk_common::uuid::Uuid;
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"PUT",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::EVENT_ID.to_string())
|
2020-05-07 19:21:06 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2020-11-25 17:50:50 +00:00
|
|
|
|
let content =
|
|
|
|
|
AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain("Hello world"));
|
2020-05-07 19:21:06 +00:00
|
|
|
|
let txn_id = Uuid::new_v4();
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let response = room.send(content, Some(txn_id)).await.unwrap();
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2020-08-04 22:56:26 +00:00
|
|
|
|
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
2020-09-15 16:06:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_attachment_send() {
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"PUT",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::EVENT_ID.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"POST",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/media/r0/upload".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-10-13 09:00:52 +00:00
|
|
|
|
.match_header("content-type", "image/jpeg")
|
2020-09-15 16:06:32 +00:00
|
|
|
|
.with_body(
|
|
|
|
|
json!({
|
|
|
|
|
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
|
|
|
|
|
})
|
|
|
|
|
.to_string(),
|
|
|
|
|
)
|
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-09-15 16:06:32 +00:00
|
|
|
|
|
|
|
|
|
let mut media = Cursor::new("Hello world");
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let response = room
|
|
|
|
|
.send_attachment("image", &mime::IMAGE_JPEG, &mut media, None)
|
2020-09-15 16:06:32 +00:00
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
2020-05-07 19:21:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-16 13:47:18 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_redact() {
|
|
|
|
|
use matrix_sdk_common::uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"PUT",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::EVENT_ID.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
|
|
|
|
|
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let event_id = event_id!("$xxxxxxxx:example.com");
|
|
|
|
|
|
|
|
|
|
let txn_id = Uuid::new_v4();
|
|
|
|
|
let reason = Some("Indecent material");
|
|
|
|
|
let response = room.redact(&event_id, reason, Some(txn_id)).await.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 10:51:53 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn user_presence() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
2020-05-07 10:51:53 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
2021-03-08 23:12:59 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::MEMBERS.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
2020-05-07 10:51:53 +00:00
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
2020-12-15 09:23:31 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2021-01-21 11:08:16 +00:00
|
|
|
|
let members: Vec<RoomMember> = room.active_members().await.unwrap();
|
2020-05-07 10:51:53 +00:00
|
|
|
|
|
2020-12-15 09:23:31 +00:00
|
|
|
|
assert_eq!(1, members.len());
|
|
|
|
|
// assert!(room.power_levels.is_some())
|
2020-05-07 10:51:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 19:21:06 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn calculate_room_names_from_summary() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-07 14:22:18 +00:00
|
|
|
|
|
2020-05-07 19:21:06 +00:00
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string())
|
2020-05-07 19:21:06 +00:00
|
|
|
|
.create();
|
2020-05-07 14:22:18 +00:00
|
|
|
|
|
2020-05-07 19:21:06 +00:00
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
2020-12-15 09:23:31 +00:00
|
|
|
|
let room = client
|
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.unwrap();
|
2020-05-07 19:21:06 +00:00
|
|
|
|
|
2021-01-18 16:31:33 +00:00
|
|
|
|
assert_eq!("example2", room.display_name().await.unwrap());
|
2020-05-07 19:21:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 17:02:36 +00:00
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn invited_rooms() {
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::INVITE_SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(SyncSettings::default()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(client.joined_rooms().is_empty());
|
|
|
|
|
assert!(client.left_rooms().is_empty());
|
|
|
|
|
assert!(!client.invited_rooms().is_empty());
|
|
|
|
|
|
|
|
|
|
assert!(client
|
|
|
|
|
.get_invited_room(&room_id!("!696r7674:example.com"))
|
|
|
|
|
.is_some());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn left_rooms() {
|
|
|
|
|
let client = logged_in_client().await;
|
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
|
|
|
|
.with_body(test_json::LEAVE_SYNC.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let _response = client.sync_once(SyncSettings::default()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(client.joined_rooms().is_empty());
|
|
|
|
|
assert!(!client.left_rooms().is_empty());
|
|
|
|
|
assert!(client.invited_rooms().is_empty());
|
|
|
|
|
|
|
|
|
|
assert!(client
|
|
|
|
|
.get_left_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
|
|
|
|
.is_some())
|
|
|
|
|
}
|
2020-05-08 09:27:33 +00:00
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn sync() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-08 09:27:33 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
2020-08-08 13:00:28 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-05-08 09:27:33 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let response = client.sync_once(sync_settings).await.unwrap();
|
2020-05-08 09:27:33 +00:00
|
|
|
|
|
|
|
|
|
assert_ne!(response.next_batch, "");
|
|
|
|
|
|
|
|
|
|
assert!(client.sync_token().await.is_some());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn room_names() {
|
2020-08-12 13:53:42 +00:00
|
|
|
|
let client = logged_in_client().await;
|
2020-05-08 09:27:33 +00:00
|
|
|
|
|
|
|
|
|
let _m = mock(
|
|
|
|
|
"GET",
|
|
|
|
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
|
|
|
|
)
|
|
|
|
|
.with_status(200)
|
2020-08-12 13:53:42 +00:00
|
|
|
|
.match_header("authorization", "Bearer 1234")
|
2020-06-22 19:40:51 +00:00
|
|
|
|
.with_body(test_json::SYNC.to_string())
|
2020-05-08 09:27:33 +00:00
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
|
|
|
|
|
2020-10-06 09:37:29 +00:00
|
|
|
|
let _response = client.sync_once(sync_settings).await.unwrap();
|
2020-05-08 09:27:33 +00:00
|
|
|
|
|
|
|
|
|
let room = client
|
2020-08-04 22:56:26 +00:00
|
|
|
|
.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
|
2020-05-08 09:27:33 +00:00
|
|
|
|
.unwrap();
|
|
|
|
|
|
2021-01-18 16:31:33 +00:00
|
|
|
|
assert_eq!("tutorial".to_string(), room.display_name().await.unwrap());
|
2020-05-08 09:27:33 +00:00
|
|
|
|
}
|
2020-10-16 15:27:00 +00:00
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn delete_devices() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
|
|
|
|
|
.with_status(401)
|
|
|
|
|
.with_body(
|
|
|
|
|
json!({
|
|
|
|
|
"flows": [
|
|
|
|
|
{
|
|
|
|
|
"stages": [
|
|
|
|
|
"m.login.password"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"params": {},
|
|
|
|
|
"session": "vBslorikviAjxzYBASOBGfPp"
|
|
|
|
|
})
|
|
|
|
|
.to_string(),
|
|
|
|
|
)
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
|
|
|
|
|
.with_status(401)
|
|
|
|
|
// empty response
|
|
|
|
|
// TODO rename that response type.
|
|
|
|
|
.with_body(test_json::LOGOUT.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let devices = &["DEVICEID".into()];
|
|
|
|
|
|
|
|
|
|
if let Err(e) = client.delete_devices(devices, None).await {
|
|
|
|
|
if let Some(info) = e.uiaa_response() {
|
|
|
|
|
let mut auth_parameters = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
let identifier = json!({
|
|
|
|
|
"type": "m.id.user",
|
|
|
|
|
"user": "example",
|
|
|
|
|
});
|
|
|
|
|
auth_parameters.insert("identifier".to_owned(), identifier);
|
|
|
|
|
auth_parameters.insert("password".to_owned(), "wordpass".into());
|
|
|
|
|
|
|
|
|
|
let auth_data = AuthData::DirectRequest {
|
|
|
|
|
kind: "m.login.password",
|
|
|
|
|
auth_parameters,
|
|
|
|
|
session: info.session.as_deref(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.delete_devices(devices, Some(auth_data))
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-01 17:25:31 +00:00
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn retry_limit_http_requests() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3));
|
|
|
|
|
assert!(config.request_config.retry_limit.unwrap() == 3);
|
|
|
|
|
let client = Client::new_with_config(homeserver, config).unwrap();
|
|
|
|
|
|
|
|
|
|
let m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(501)
|
|
|
|
|
.expect(3)
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
if client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.is_err()
|
|
|
|
|
{
|
|
|
|
|
m.assert();
|
|
|
|
|
} else {
|
|
|
|
|
panic!("this request should return an `Err` variant")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn retry_timeout_http_requests() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
// Keep this timeout small so that the test doesn't take long
|
|
|
|
|
let retry_timeout = Duration::from_secs(5);
|
|
|
|
|
let config = ClientConfig::default()
|
|
|
|
|
.request_config(RequestConfig::new().retry_timeout(retry_timeout));
|
|
|
|
|
assert!(config.request_config.retry_timeout.unwrap() == retry_timeout);
|
|
|
|
|
let client = Client::new_with_config(homeserver, config).unwrap();
|
|
|
|
|
|
|
|
|
|
let m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(501)
|
|
|
|
|
.expect_at_least(2)
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
if client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.is_err()
|
|
|
|
|
{
|
|
|
|
|
m.assert();
|
|
|
|
|
} else {
|
|
|
|
|
panic!("this request should return an `Err` variant")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn no_retry_http_requests() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
let config = ClientConfig::default().request_config(RequestConfig::new().disable_retry());
|
|
|
|
|
assert!(config.request_config.retry_limit.unwrap() == 0);
|
|
|
|
|
let client = Client::new_with_config(homeserver, config).unwrap();
|
|
|
|
|
|
|
|
|
|
let m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(501)
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
if client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.is_err()
|
|
|
|
|
{
|
|
|
|
|
m.assert();
|
|
|
|
|
} else {
|
|
|
|
|
panic!("this request should return an `Err` variant")
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-07 00:59:44 +00:00
|
|
|
|
}
|