// 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. use std::ops::Deref; use matrix_sdk_common::{ api::r0::push::get_notifications::Notification, async_trait, events::{ fully_read::FullyReadEventContent, AnySyncRoomEvent, GlobalAccountDataEvent, RoomAccountDataEvent, }, identifiers::RoomId, }; use serde_json::value::RawValue as RawJsonValue; use crate::{ deserialized_responses::SyncResponse, events::{ call::{ answer::AnswerEventContent, candidates::CandidatesEventContent, hangup::HangupEventContent, invite::InviteEventContent, }, custom::CustomEventContent, ignored_user_list::IgnoredUserListEventContent, presence::PresenceEvent, push_rules::PushRulesEventContent, receipt::ReceiptEventContent, room::{ aliases::AliasesEventContent, avatar::AvatarEventContent, canonical_alias::CanonicalAliasEventContent, join_rules::JoinRulesEventContent, member::MemberEventContent, message::{feedback::FeedbackEventContent, MessageEventContent as MsgEventContent}, name::NameEventContent, power_levels::PowerLevelsEventContent, redaction::SyncRedactionEvent, tombstone::TombstoneEventContent, }, typing::TypingEventContent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncMessageEvent, AnySyncStateEvent, StrippedStateEvent, SyncEphemeralRoomEvent, SyncMessageEvent, SyncStateEvent, }, room::Room, Client, }; pub(crate) struct Handler { pub(crate) inner: Box, pub(crate) client: Client, } impl Deref for Handler { type Target = dyn EventHandler; fn deref(&self) -> &Self::Target { &*self.inner } } impl Handler { fn get_room(&self, room_id: &RoomId) -> Option { self.client.get_room(room_id) } pub(crate) async fn handle_sync(&self, response: &SyncResponse) { for event in response.account_data.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_account_data_event(&event).await; } for (room_id, room_info) in &response.rooms.join { if let Some(room) = self.get_room(room_id) { for event in room_info.ephemeral.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_ephemeral_event(room.clone(), &event).await; } for event in room_info.account_data.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_room_account_data_event(room.clone(), &event).await; } for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_state_event(room.clone(), &event).await; } for event in room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok()) { self.handle_timeline_event(room.clone(), &event).await; } } } for (room_id, room_info) in &response.rooms.leave { if let Some(room) = self.get_room(room_id) { for event in room_info.account_data.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_room_account_data_event(room.clone(), &event).await; } for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_state_event(room.clone(), &event).await; } for event in room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok()) { self.handle_timeline_event(room.clone(), &event).await; } } } for (room_id, room_info) in &response.rooms.invite { if let Some(room) = self.get_room(room_id) { for event in room_info.invite_state.events.iter().filter_map(|e| e.deserialize().ok()) { self.handle_stripped_state_event(room.clone(), &event).await; } } } for event in response.presence.events.iter().filter_map(|e| e.deserialize().ok()) { self.on_presence_event(&event).await; } for (room_id, notifications) in &response.notifications { if let Some(room) = self.get_room(&room_id) { for notification in notifications { self.on_room_notification(room.clone(), notification.clone()).await; } } } } async fn handle_timeline_event(&self, room: Room, event: &AnySyncRoomEvent) { match event { AnySyncRoomEvent::State(event) => match event { AnySyncStateEvent::RoomMember(e) => self.on_room_member(room, e).await, AnySyncStateEvent::RoomName(e) => self.on_room_name(room, e).await, AnySyncStateEvent::RoomCanonicalAlias(e) => { self.on_room_canonical_alias(room, e).await } AnySyncStateEvent::RoomAliases(e) => self.on_room_aliases(room, e).await, AnySyncStateEvent::RoomAvatar(e) => self.on_room_avatar(room, e).await, AnySyncStateEvent::RoomPowerLevels(e) => self.on_room_power_levels(room, e).await, AnySyncStateEvent::RoomTombstone(e) => self.on_room_tombstone(room, e).await, AnySyncStateEvent::RoomJoinRules(e) => self.on_room_join_rules(room, e).await, AnySyncStateEvent::Custom(e) => { self.on_custom_event(room, &CustomEvent::State(e)).await } _ => {} }, AnySyncRoomEvent::Message(event) => match event { AnySyncMessageEvent::RoomMessage(e) => self.on_room_message(room, e).await, AnySyncMessageEvent::RoomMessageFeedback(e) => { self.on_room_message_feedback(room, e).await } AnySyncMessageEvent::RoomRedaction(e) => self.on_room_redaction(room, e).await, AnySyncMessageEvent::Custom(e) => { self.on_custom_event(room, &CustomEvent::Message(e)).await } AnySyncMessageEvent::CallInvite(e) => self.on_room_call_invite(room, e).await, AnySyncMessageEvent::CallAnswer(e) => self.on_room_call_answer(room, e).await, AnySyncMessageEvent::CallCandidates(e) => { self.on_room_call_candidates(room, e).await } AnySyncMessageEvent::CallHangup(e) => self.on_room_call_hangup(room, e).await, _ => {} }, AnySyncRoomEvent::RedactedState(_event) => {} AnySyncRoomEvent::RedactedMessage(_event) => {} } } async fn handle_state_event(&self, room: Room, event: &AnySyncStateEvent) { match event { AnySyncStateEvent::RoomMember(member) => self.on_state_member(room, &member).await, AnySyncStateEvent::RoomName(name) => self.on_state_name(room, &name).await, AnySyncStateEvent::RoomCanonicalAlias(canonical) => { self.on_state_canonical_alias(room, &canonical).await } AnySyncStateEvent::RoomAliases(aliases) => self.on_state_aliases(room, &aliases).await, AnySyncStateEvent::RoomAvatar(avatar) => self.on_state_avatar(room, &avatar).await, AnySyncStateEvent::RoomPowerLevels(power) => { self.on_state_power_levels(room, &power).await } AnySyncStateEvent::RoomJoinRules(rules) => self.on_state_join_rules(room, &rules).await, AnySyncStateEvent::RoomTombstone(tomb) => { // TODO make `on_state_tombstone` method self.on_room_tombstone(room, &tomb).await } AnySyncStateEvent::Custom(custom) => { self.on_custom_event(room, &CustomEvent::State(custom)).await } _ => {} } } pub(crate) async fn handle_stripped_state_event( &self, // TODO these events are only handled in invited rooms. room: Room, event: &AnyStrippedStateEvent, ) { match event { AnyStrippedStateEvent::RoomMember(member) => { self.on_stripped_state_member(room, &member, None).await } AnyStrippedStateEvent::RoomName(name) => self.on_stripped_state_name(room, &name).await, AnyStrippedStateEvent::RoomCanonicalAlias(canonical) => { self.on_stripped_state_canonical_alias(room, &canonical).await } AnyStrippedStateEvent::RoomAliases(aliases) => { self.on_stripped_state_aliases(room, &aliases).await } AnyStrippedStateEvent::RoomAvatar(avatar) => { self.on_stripped_state_avatar(room, &avatar).await } AnyStrippedStateEvent::RoomPowerLevels(power) => { self.on_stripped_state_power_levels(room, &power).await } AnyStrippedStateEvent::RoomJoinRules(rules) => { self.on_stripped_state_join_rules(room, &rules).await } _ => {} } } pub(crate) async fn handle_room_account_data_event( &self, room: Room, event: &AnyRoomAccountDataEvent, ) { if let AnyRoomAccountDataEvent::FullyRead(event) = event { self.on_non_room_fully_read(room, &event).await } } pub(crate) async fn handle_account_data_event(&self, event: &AnyGlobalAccountDataEvent) { match event { AnyGlobalAccountDataEvent::IgnoredUserList(ignored) => { self.on_non_room_ignored_users(&ignored).await } AnyGlobalAccountDataEvent::PushRules(rules) => { self.on_non_room_push_rules(&rules).await } _ => {} } } pub(crate) async fn handle_ephemeral_event( &self, room: Room, event: &AnySyncEphemeralRoomEvent, ) { match event { AnySyncEphemeralRoomEvent::Typing(typing) => { self.on_non_room_typing(room, typing).await } AnySyncEphemeralRoomEvent::Receipt(receipt) => { self.on_non_room_receipt(room, receipt).await } _ => {} } } } /// This represents the various "unrecognized" events. #[derive(Clone, Copy, Debug)] pub enum CustomEvent<'c> { /// A custom basic event. Basic(&'c GlobalAccountDataEvent), /// A custom basic event. EphemeralRoom(&'c SyncEphemeralRoomEvent), /// A custom room event. Message(&'c SyncMessageEvent), /// A custom state event. State(&'c SyncStateEvent), /// A custom stripped state event. StrippedState(&'c StrippedStateEvent), } /// This trait allows any type implementing `EventHandler` to specify event /// callbacks for each event. The `Client` calls each method when the /// corresponding event is received. /// /// # Examples /// ``` /// # use std::ops::Deref; /// # use std::sync::Arc; /// # use std::{env, process::exit}; /// # use matrix_sdk::{ /// # async_trait, /// # EventHandler, /// # events::{ /// # room::message::{MessageEventContent, MessageType, TextMessageEventContent}, /// # SyncMessageEvent /// # }, /// # locks::RwLock, /// # room::Room, /// # }; /// /// struct EventCallback; /// /// #[async_trait] /// impl EventHandler for EventCallback { /// async fn on_room_message(&self, room: Room, event: &SyncMessageEvent) { /// if let Room::Joined(room) = room { /// if let SyncMessageEvent { /// content: /// MessageEventContent { /// msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), /// .. /// }, /// sender, /// .. /// } = event /// { /// let member = room.get_member(&sender).await.unwrap().unwrap(); /// let name = member /// .display_name() /// .unwrap_or_else(|| member.user_id().as_str()); /// println!("{}: {}", name, msg_body); /// } /// } /// } /// } /// ``` #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait EventHandler: Send + Sync { // ROOM EVENTS from `IncomingTimeline` /// Fires when `Client` receives a `RoomEvent::RoomMember` event. async fn on_room_member(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomName` event. async fn on_room_name(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomCanonicalAlias` event. async fn on_room_canonical_alias( &self, _: Room, _: &SyncStateEvent, ) { } /// Fires when `Client` receives a `RoomEvent::RoomAliases` event. async fn on_room_aliases(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomAvatar` event. async fn on_room_avatar(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomMessage` event. async fn on_room_message(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomMessageFeedback` event. async fn on_room_message_feedback(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::CallInvite` event async fn on_room_call_invite(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::CallAnswer` event async fn on_room_call_answer(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::CallCandidates` event async fn on_room_call_candidates(&self, _: Room, _: &SyncMessageEvent) { } /// Fires when `Client` receives a `RoomEvent::CallHangup` event async fn on_room_call_hangup(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomRedaction` event. async fn on_room_redaction(&self, _: Room, _: &SyncRedactionEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event. async fn on_room_power_levels(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomJoinRules` event. async fn on_room_join_rules(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::Tombstone` event. async fn on_room_tombstone(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives room events that trigger notifications /// according to the push rules of the user. async fn on_room_notification(&self, _: Room, _: Notification) {} // `RoomEvent`s from `IncomingState` /// Fires when `Client` receives a `StateEvent::RoomMember` event. async fn on_state_member(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomName` event. async fn on_state_name(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomCanonicalAlias` event. async fn on_state_canonical_alias( &self, _: Room, _: &SyncStateEvent, ) { } /// Fires when `Client` receives a `StateEvent::RoomAliases` event. async fn on_state_aliases(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomAvatar` event. async fn on_state_avatar(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomPowerLevels` event. async fn on_state_power_levels(&self, _: Room, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomJoinRules` event. async fn on_state_join_rules(&self, _: Room, _: &SyncStateEvent) {} // `AnyStrippedStateEvent`s /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomMember` event. async fn on_stripped_state_member( &self, _: Room, _: &StrippedStateEvent, _: Option, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomName` /// event. async fn on_stripped_state_name(&self, _: Room, _: &StrippedStateEvent) {} /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomCanonicalAlias` event. async fn on_stripped_state_canonical_alias( &self, _: Room, _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomAliases` event. async fn on_stripped_state_aliases( &self, _: Room, _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomAvatar` event. async fn on_stripped_state_avatar(&self, _: Room, _: &StrippedStateEvent) {} /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomPowerLevels` event. async fn on_stripped_state_power_levels( &self, _: Room, _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomJoinRules` event. async fn on_stripped_state_join_rules( &self, _: Room, _: &StrippedStateEvent, ) { } // `NonRoomEvent` (this is a type alias from ruma_events) /// Fires when `Client` receives a `NonRoomEvent::RoomPresence` event. async fn on_non_room_presence(&self, _: Room, _: &PresenceEvent) {} /// Fires when `Client` receives a `NonRoomEvent::RoomName` event. async fn on_non_room_ignored_users( &self, _: &GlobalAccountDataEvent, ) { } /// Fires when `Client` receives a `NonRoomEvent::RoomCanonicalAlias` event. async fn on_non_room_push_rules(&self, _: &GlobalAccountDataEvent) {} /// Fires when `Client` receives a `NonRoomEvent::RoomAliases` event. async fn on_non_room_fully_read( &self, _: Room, _: &RoomAccountDataEvent, ) { } /// Fires when `Client` receives a `NonRoomEvent::Typing` event. async fn on_non_room_typing(&self, _: Room, _: &SyncEphemeralRoomEvent) {} /// Fires when `Client` receives a `NonRoomEvent::Receipt` event. /// /// This is always a read receipt. async fn on_non_room_receipt(&self, _: Room, _: &SyncEphemeralRoomEvent) {} // `PresenceEvent` is a struct so there is only the one method /// Fires when `Client` receives a `NonRoomEvent::RoomAliases` event. async fn on_presence_event(&self, _: &PresenceEvent) {} /// Fires when `Client` receives a `Event::Custom` event or if /// deserialization fails because the event was unknown to ruma. /// /// The only guarantee this method can give about the event is that it is /// valid JSON. async fn on_unrecognized_event(&self, _: Room, _: &RawJsonValue) {} /// Fires when `Client` receives a `Event::Custom` event or if /// deserialization fails because the event was unknown to ruma. /// /// The only guarantee this method can give about the event is that it is in /// the shape of a valid matrix event. async fn on_custom_event(&self, _: Room, _: &CustomEvent<'_>) {} } #[cfg(test)] mod test { use std::{sync::Arc, time::Duration}; use matrix_sdk_common::{async_trait, locks::Mutex}; use matrix_sdk_test::{async_test, test_json}; use mockito::{mock, Matcher}; #[cfg(target_arch = "wasm32")] pub use wasm_bindgen_test::*; use super::*; #[derive(Clone)] pub struct EvHandlerTest(Arc>>); #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl EventHandler for EvHandlerTest { async fn on_room_member(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("member".to_string()) } async fn on_room_name(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("name".to_string()) } async fn on_room_canonical_alias( &self, _: Room, _: &SyncStateEvent, ) { self.0.lock().await.push("canonical".to_string()) } async fn on_room_aliases(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("aliases".to_string()) } async fn on_room_avatar(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("avatar".to_string()) } async fn on_room_message(&self, _: Room, _: &SyncMessageEvent) { self.0.lock().await.push("message".to_string()) } async fn on_room_message_feedback( &self, _: Room, _: &SyncMessageEvent, ) { self.0.lock().await.push("feedback".to_string()) } async fn on_room_call_invite(&self, _: Room, _: &SyncMessageEvent) { self.0.lock().await.push("call invite".to_string()) } async fn on_room_call_answer(&self, _: Room, _: &SyncMessageEvent) { self.0.lock().await.push("call answer".to_string()) } async fn on_room_call_candidates( &self, _: Room, _: &SyncMessageEvent, ) { self.0.lock().await.push("call candidates".to_string()) } async fn on_room_call_hangup(&self, _: Room, _: &SyncMessageEvent) { self.0.lock().await.push("call hangup".to_string()) } async fn on_room_redaction(&self, _: Room, _: &SyncRedactionEvent) { self.0.lock().await.push("redaction".to_string()) } async fn on_room_power_levels(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("power".to_string()) } async fn on_room_tombstone(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("tombstone".to_string()) } async fn on_state_member(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("state member".to_string()) } async fn on_state_name(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("state name".to_string()) } async fn on_state_canonical_alias( &self, _: Room, _: &SyncStateEvent, ) { self.0.lock().await.push("state canonical".to_string()) } async fn on_state_aliases(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("state aliases".to_string()) } async fn on_state_avatar(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("state avatar".to_string()) } async fn on_state_power_levels( &self, _: Room, _: &SyncStateEvent, ) { self.0.lock().await.push("state power".to_string()) } async fn on_state_join_rules(&self, _: Room, _: &SyncStateEvent) { self.0.lock().await.push("state rules".to_string()) } // `AnyStrippedStateEvent`s /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomMember` event. async fn on_stripped_state_member( &self, _: Room, _: &StrippedStateEvent, _: Option, ) { self.0.lock().await.push("stripped state member".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomName` event. async fn on_stripped_state_name(&self, _: Room, _: &StrippedStateEvent) { self.0.lock().await.push("stripped state name".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomCanonicalAlias` event. async fn on_stripped_state_canonical_alias( &self, _: Room, _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state canonical".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomAliases` event. async fn on_stripped_state_aliases( &self, _: Room, _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state aliases".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomAvatar` event. async fn on_stripped_state_avatar( &self, _: Room, _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state avatar".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomPowerLevels` event. async fn on_stripped_state_power_levels( &self, _: Room, _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state power".to_string()) } /// Fires when `Client` receives a /// `AnyStrippedStateEvent::StrippedRoomJoinRules` event. async fn on_stripped_state_join_rules( &self, _: Room, _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state rules".to_string()) } async fn on_non_room_presence(&self, _: Room, _: &PresenceEvent) { self.0.lock().await.push("presence".to_string()) } async fn on_non_room_ignored_users( &self, _: &GlobalAccountDataEvent, ) { self.0.lock().await.push("account ignore".to_string()) } async fn on_non_room_push_rules(&self, _: &GlobalAccountDataEvent) { self.0.lock().await.push("account push rules".to_string()) } async fn on_non_room_fully_read( &self, _: Room, _: &RoomAccountDataEvent, ) { self.0.lock().await.push("account read".to_string()) } async fn on_non_room_typing( &self, _: Room, _: &SyncEphemeralRoomEvent, ) { self.0.lock().await.push("typing event".to_string()) } async fn on_non_room_receipt( &self, _: Room, _: &SyncEphemeralRoomEvent, ) { self.0.lock().await.push("receipt event".to_string()) } async fn on_presence_event(&self, _: &PresenceEvent) { self.0.lock().await.push("presence event".to_string()) } async fn on_unrecognized_event(&self, _: Room, _: &RawJsonValue) { self.0.lock().await.push("unrecognized event".to_string()) } async fn on_custom_event(&self, _: Room, _: &CustomEvent<'_>) { self.0.lock().await.push("custom event".to_string()) } async fn on_room_notification(&self, _: Room, _: Notification) { self.0.lock().await.push("notification".to_string()) } } use crate::{identifiers::user_id, Client, Session, SyncSettings}; async fn get_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 } async fn mock_sync(client: &Client, response: String) { let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string())) .with_status(200) .match_header("authorization", "Bearer 1234") .with_body(response) .create(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); let _response = client.sync_once(sync_settings).await.unwrap(); } #[async_test] async fn event_handler_joined() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!( v.as_slice(), [ "account ignore", "receipt event", "account read", "state rules", "state member", "state aliases", "state power", "state canonical", "state member", "state member", "message", "presence event", "notification", ], ) } #[async_test] async fn event_handler_invite() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::INVITE_SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!(v.as_slice(), ["stripped state name", "stripped state member", "presence event"],) } #[async_test] async fn event_handler_leave() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::LEAVE_SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!( v.as_slice(), [ "account ignore", "state rules", "state member", "state aliases", "state power", "state canonical", "state member", "state member", "message", "presence event", "notification", ], ) } #[async_test] async fn event_handler_more_events() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::MORE_SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!( v.as_slice(), [ "receipt event", "typing event", "message", "message", // this is a message edit event "redaction", "message", // this is a notice event ], ) } #[async_test] async fn event_handler_voip() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::VOIP_SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!(v.as_slice(), ["call invite", "call answer", "call candidates", "call hangup",],) } #[async_test] async fn event_handler_two_syncs() { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let handler = Box::new(EvHandlerTest(vec)); let client = get_client().await; client.set_event_handler(handler).await; mock_sync(&client, test_json::SYNC.to_string()).await; mock_sync(&client, test_json::MORE_SYNC.to_string()).await; let v = test_vec.lock().await; assert_eq!( v.as_slice(), [ "account ignore", "receipt event", "account read", "state rules", "state member", "state aliases", "state power", "state canonical", "state member", "state member", "message", "presence event", "notification", "receipt event", "typing event", "message", "message", // this is a message edit event "redaction", "message", // this is a notice event "notification", "notification", "notification", ], ) } }