[fractal] API: Use RoomId from ruma-identifiers for stronger validation



commit d39244aaa06114b69df9e66ea23aa405a4bc8e3f
Author: Alejandro Domínguez <adomu net-c com>
Date:   Fri Dec 27 06:10:09 2019 +0100

    API: Use RoomId from ruma-identifiers for stronger validation

 fractal-gtk/src/actions/global.rs             |   8 +-
 fractal-gtk/src/actions/room_history.rs       |  10 +-
 fractal-gtk/src/actions/room_settings.rs      |   9 +-
 fractal-gtk/src/appop/invite.rs               |  11 +-
 fractal-gtk/src/appop/member.rs               |  12 +-
 fractal-gtk/src/appop/message.rs              |  15 +-
 fractal-gtk/src/appop/mod.rs                  |   9 +-
 fractal-gtk/src/appop/notifications.rs        |  13 +-
 fractal-gtk/src/appop/notify.rs               |   3 +-
 fractal-gtk/src/appop/room.rs                 | 311 +++++++++++-----------
 fractal-gtk/src/appop/start_chat.rs           |  15 +-
 fractal-gtk/src/appop/state.rs                |   6 +-
 fractal-gtk/src/widgets/room.rs               |   8 +-
 fractal-gtk/src/widgets/room_history.rs       |   3 +-
 fractal-gtk/src/widgets/room_settings.rs      |   6 +-
 fractal-gtk/src/widgets/roomlist.rs           | 165 ++++++------
 fractal-gtk/src/widgets/roomrow.rs            |   8 +-
 fractal-gtk/src/widgets/scroll_widget.rs      |   7 +-
 fractal-matrix-api/src/backend/directory.rs   |   2 +-
 fractal-matrix-api/src/backend/media.rs       |  13 +-
 fractal-matrix-api/src/backend/mod.rs         | 126 +++++----
 fractal-matrix-api/src/backend/room.rs        | 364 +++++++++++---------------
 fractal-matrix-api/src/backend/sync.rs        |  12 +-
 fractal-matrix-api/src/backend/types.rs       |  68 ++---
 fractal-matrix-api/src/model/event.rs         |   3 +-
 fractal-matrix-api/src/model/message.rs       |  78 +++---
 fractal-matrix-api/src/model/room.rs          |  31 ++-
 fractal-matrix-api/src/r0/sync/sync_events.rs |   7 +-
 fractal-matrix-api/src/util.rs                |   5 +-
 29 files changed, 668 insertions(+), 660 deletions(-)
---
diff --git a/fractal-gtk/src/actions/global.rs b/fractal-gtk/src/actions/global.rs
index fcf6742f..ae8aaaba 100644
--- a/fractal-gtk/src/actions/global.rs
+++ b/fractal-gtk/src/actions/global.rs
@@ -1,6 +1,7 @@
 use fractal_api::clone;
 use log::{debug, info};
 use std::cell::RefCell;
+use std::convert::TryInto;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 
@@ -8,6 +9,7 @@ use crate::appop::AppOp;
 use crate::i18n::i18n;
 use crate::widgets::FileDialog::open;
 use crate::App;
+use fractal_api::identifiers::RoomId;
 use fractal_api::types::Message;
 use gio::prelude::*;
 use gio::SimpleAction;
@@ -224,7 +226,7 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
     let back_weak = Rc::downgrade(&back_history);
     open_room.connect_activate(clone!(op => move |_, data| {
         if let Some(id) = get_room_id(data) {
-            op.lock().unwrap().set_active_room_by_id(id.to_string());
+            op.lock().unwrap().set_active_room_by_id(id);
            /* This does nothing if fractal is already in focus */
             op.lock().unwrap().activate();
         }
@@ -321,8 +323,8 @@ pub fn new(app: &gtk::Application, op: &Arc<Mutex<AppOp>>) {
     //op.lock().unwrap().mark_active_room_messages();
 }
 
-fn get_room_id(data: Option<&glib::Variant>) -> Option<&str> {
-    data?.get_str()
+fn get_room_id(data: Option<&glib::Variant>) -> Option<RoomId> {
+    data?.get_str().and_then(|rid| rid.try_into().ok())
 }
 
 fn get_message(data: Option<&glib::Variant>) -> Option<Message> {
diff --git a/fractal-gtk/src/actions/room_history.rs b/fractal-gtk/src/actions/room_history.rs
index 1308ff63..eb9e0bdd 100644
--- a/fractal-gtk/src/actions/room_history.rs
+++ b/fractal-gtk/src/actions/room_history.rs
@@ -1,6 +1,8 @@
 use fractal_api::clone;
+use fractal_api::identifiers::RoomId;
 use fractal_api::r0::AccessToken;
 use log::error;
+use std::convert::TryFrom;
 use std::fs;
 use std::sync::mpsc::channel;
 use std::sync::mpsc::TryRecvError;
@@ -215,15 +217,17 @@ fn get_message_by_id(id: &str) -> Option<Message> {
     op.get_message_by_id(room_id, id)
 }
 
-fn get_room_id(data: Option<&glib::Variant>) -> Option<String> {
-    data.as_ref()?.get_str().map(|s| s.to_string())
+fn get_room_id(data: Option<&glib::Variant>) -> Option<RoomId> {
+    data.as_ref()?
+        .get_str()
+        .and_then(|rid| RoomId::try_from(rid).ok())
 }
 
 fn request_more_messages(
     backend: &Sender<BKCommand>,
     server_url: Url,
     access_token: AccessToken,
-    id: Option<String>,
+    id: Option<RoomId>,
 ) -> Option<()> {
     let op = App::get_op()?;
     let op = op.lock().unwrap();
diff --git a/fractal-gtk/src/actions/room_settings.rs b/fractal-gtk/src/actions/room_settings.rs
index eaac18c4..07c294db 100644
--- a/fractal-gtk/src/actions/room_settings.rs
+++ b/fractal-gtk/src/actions/room_settings.rs
@@ -1,3 +1,4 @@
+use fractal_api::identifiers::RoomId;
 use fractal_api::r0::AccessToken;
 use fractal_api::url::Url;
 use gio::prelude::*;
@@ -6,6 +7,7 @@ use gio::SimpleActionGroup;
 use glib;
 use gtk;
 use gtk::prelude::*;
+use std::convert::TryFrom;
 use std::sync::mpsc::Sender;
 
 use crate::backend::BKCommand;
@@ -36,7 +38,10 @@ pub fn new(
     let window_weak = window.downgrade();
     let backend = backend.clone();
     change_avatar.connect_activate(move |a, data| {
-        if let Some(id) = data.as_ref().and_then(|x| x.get_str()) {
+        if let Some(id) = data
+            .and_then(|x| x.get_str())
+            .and_then(|rid| RoomId::try_from(rid).ok())
+        {
             let window = upgrade_weak!(window_weak);
             let filter = gtk::FileFilter::new();
             filter.set_name(Some(i18n("Images").as_str()));
@@ -47,7 +52,7 @@ pub fn new(
                     let _ = backend.send(BKCommand::SetRoomAvatar(
                         server_url.clone(),
                         access_token.clone(),
-                        id.to_string(),
+                        id,
                         file.to_string(),
                     ));
                 } else {
diff --git a/fractal-gtk/src/appop/invite.rs b/fractal-gtk/src/appop/invite.rs
index aa5bd09c..d1e467a4 100644
--- a/fractal-gtk/src/appop/invite.rs
+++ b/fractal-gtk/src/appop/invite.rs
@@ -1,5 +1,6 @@
 use crate::i18n::{i18n, i18n_k};
 
+use fractal_api::identifiers::RoomId;
 use gtk;
 use gtk::prelude::*;
 
@@ -209,15 +210,14 @@ impl AppOp {
         dialog.resize(300, 200);
     }
 
-    pub fn remove_inv(&mut self, roomid: String) {
-        self.rooms.remove(&roomid);
-        self.roomlist.remove_room(roomid);
+    pub fn remove_inv(&mut self, room_id: RoomId) {
+        self.rooms.remove(&room_id);
+        self.roomlist.remove_room(room_id);
     }
 
     pub fn accept_inv(&mut self, accept: bool) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
-        let rid = self.invitation_roomid.clone();
-        if let Some(rid) = rid {
+        if let Some(rid) = self.invitation_roomid.take() {
             if accept {
                 self.backend
                     .send(BKCommand::AcceptInv(
@@ -237,7 +237,6 @@ impl AppOp {
             }
             self.remove_inv(rid);
         }
-        self.invitation_roomid = None;
     }
 
     /* FIXME: move to a widget */
diff --git a/fractal-gtk/src/appop/member.rs b/fractal-gtk/src/appop/member.rs
index 9e60ccc3..c638337b 100644
--- a/fractal-gtk/src/appop/member.rs
+++ b/fractal-gtk/src/appop/member.rs
@@ -1,4 +1,5 @@
 use fractal_api::clone;
+use fractal_api::identifiers::RoomId;
 use gtk;
 use gtk::prelude::*;
 
@@ -23,8 +24,9 @@ pub enum SearchType {
 impl AppOp {
     pub fn member_level(&self, member: &Member) -> i32 {
         if let Some(r) = self
-            .rooms
-            .get(&self.active_room.clone().unwrap_or_default())
+            .active_room
+            .as_ref()
+            .and_then(|a_room| self.rooms.get(a_room))
         {
             if let Some(level) = r.admins.get(&member.uid) {
                 return *level;
@@ -33,15 +35,15 @@ impl AppOp {
         0
     }
 
-    pub fn set_room_members(&mut self, roomid: String, members: Vec<Member>) {
-        if let Some(r) = self.rooms.get_mut(&roomid) {
+    pub fn set_room_members(&mut self, room_id: RoomId, members: Vec<Member>) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             r.members = HashMap::new();
             for m in members {
                 r.members.insert(m.uid.clone(), m);
             }
         }
 
-        self.recalculate_room_name(roomid.clone());
+        self.recalculate_room_name(room_id.clone());
 
         /* FIXME: update the current room settings insteat of creating a new one */
         if self.room_settings.is_some() && self.state == AppState::RoomSettings {
diff --git a/fractal-gtk/src/appop/message.rs b/fractal-gtk/src/appop/message.rs
index d1c8365a..a56952a4 100644
--- a/fractal-gtk/src/appop/message.rs
+++ b/fractal-gtk/src/appop/message.rs
@@ -1,4 +1,5 @@
 use comrak::{markdown_to_html, ComrakOptions};
+use fractal_api::identifiers::RoomId;
 use gdk_pixbuf::Pixbuf;
 use gio::prelude::FileExt;
 use gtk;
@@ -29,7 +30,7 @@ pub struct TmpMsg {
 }
 
 impl AppOp {
-    pub fn get_message_by_id(&self, room_id: &str, id: &str) -> Option<Message> {
+    pub fn get_message_by_id(&self, room_id: &RoomId, id: &str) -> Option<Message> {
         let room = self.rooms.get(room_id)?;
         room.messages
             .iter()
@@ -401,18 +402,18 @@ impl AppOp {
     pub fn show_room_messages_top(
         &mut self,
         msgs: Vec<Message>,
-        roomid: String,
+        room_id: RoomId,
         prev_batch: Option<String>,
     ) {
-        if let Some(r) = self.rooms.get_mut(&roomid) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             r.prev_batch = prev_batch;
         }
 
-        let active_room = self.active_room.clone().unwrap_or_default();
+        let active_room = self.active_room.as_ref();
         let mut list = vec![];
         for item in msgs.iter().rev() {
             /* create a list of new messages to load to the history */
-            if item.room == active_room && !item.redacted {
+            if active_room.map_or(false, |a_room| item.room == *a_room) && !item.redacted {
                 if let Some(ui_msg) = self.create_new_room_message(item) {
                     list.push(ui_msg);
                 }
@@ -428,8 +429,8 @@ impl AppOp {
         }
     }
 
-    pub fn remove_message(&mut self, room: String, id: String) -> Option<()> {
-        let message = self.get_message_by_id(&room, &id);
+    pub fn remove_message(&mut self, room_id: RoomId, id: String) -> Option<()> {
+        let message = self.get_message_by_id(&room_id, &id);
 
         if let Some(msg) = message {
             self.remove_room_message(&msg);
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index 82168aaf..6fab3629 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -3,6 +3,7 @@ use std::collections::HashMap;
 use std::rc::Rc;
 use std::sync::mpsc::Sender;
 
+use fractal_api::identifiers::RoomId;
 use fractal_api::r0::AccessToken;
 
 use gtk;
@@ -66,20 +67,20 @@ pub struct AppOp {
     pub login_data: Option<LoginData>,
     pub device_id: Option<String>,
 
-    pub active_room: Option<String>,
+    pub active_room: Option<RoomId>,
     pub rooms: RoomList,
     pub room_settings: Option<widgets::RoomSettings>,
     pub history: Option<widgets::RoomHistory>,
     pub roomlist: widgets::RoomList,
-    pub unsent_messages: HashMap<String, (String, i32)>,
-    pub typing: HashMap<String, std::time::Instant>,
+    pub unsent_messages: HashMap<RoomId, (String, i32)>,
+    pub typing: HashMap<RoomId, std::time::Instant>,
 
     pub media_viewer: Rc<RefCell<Option<widgets::MediaViewer>>>,
 
     pub state: AppState,
     pub since: Option<String>,
 
-    pub invitation_roomid: Option<String>,
+    pub invitation_roomid: Option<RoomId>,
     pub md_enabled: bool,
     pub invite_list: Vec<(Member, gtk::TextChildAnchor)>,
     search_type: SearchType,
diff --git a/fractal-gtk/src/appop/notifications.rs b/fractal-gtk/src/appop/notifications.rs
index feb5d307..fee22e1d 100644
--- a/fractal-gtk/src/appop/notifications.rs
+++ b/fractal-gtk/src/appop/notifications.rs
@@ -1,17 +1,18 @@
 use crate::appop::AppOp;
+use fractal_api::identifiers::RoomId;
 
 impl AppOp {
-    pub fn clear_room_notifications(&mut self, r: String) {
-        self.set_room_notifications(r.clone(), 0, 0);
-        self.roomlist.set_bold(r, false);
+    pub fn clear_room_notifications(&mut self, room_id: RoomId) {
+        self.set_room_notifications(room_id.clone(), 0, 0);
+        self.roomlist.set_bold(room_id, false);
     }
 
-    pub fn set_room_notifications(&mut self, roomid: String, n: i32, h: i32) {
-        if let Some(r) = self.rooms.get_mut(&roomid) {
+    pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             r.notifications = n;
             r.highlight = h;
             self.roomlist
-                .set_room_notifications(roomid, r.notifications, r.highlight);
+                .set_room_notifications(room_id, r.notifications, r.highlight);
         }
     }
 }
diff --git a/fractal-gtk/src/appop/notify.rs b/fractal-gtk/src/appop/notify.rs
index ed964c9b..4f2c1594 100644
--- a/fractal-gtk/src/appop/notify.rs
+++ b/fractal-gtk/src/appop/notify.rs
@@ -1,3 +1,4 @@
+use fractal_api::identifiers::RoomId;
 use gio::ApplicationExt;
 use gio::FileExt;
 use gio::Notification;
@@ -40,7 +41,7 @@ impl AppOp {
         inapp.set_reveal_child(false);
     }
 
-    pub fn notify(&self, app: gtk::Application, room_id: &str, id: &str) -> Option<()> {
+    pub fn notify(&self, app: gtk::Application, room_id: &RoomId, id: &str) -> Option<()> {
         let server_url = self.login_data.clone()?.server_url;
         let msg = self.get_message_by_id(room_id, id)?;
         let r = self.rooms.get(room_id)?;
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index b2cb88b9..3e146e54 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -1,6 +1,8 @@
 use crate::i18n::{i18n, i18n_k, ni18n_f};
+use fractal_api::identifiers::RoomId;
 use fractal_api::url::Url;
 use log::{error, warn};
+use std::convert::TryFrom;
 use std::fs::remove_file;
 use std::os::unix::fs;
 
@@ -11,6 +13,7 @@ use crate::appop::AppOp;
 
 use crate::backend;
 use crate::backend::BKCommand;
+use crate::backend::BKResponse;
 use fractal_api::util::cache_dir_path;
 
 use crate::actions;
@@ -22,9 +25,6 @@ use crate::types::{Member, Reason, Room, RoomMembership, RoomTag};
 
 use crate::util::markup_text;
 
-use rand::distributions::Alphanumeric;
-use rand::{thread_rng, Rng};
-
 use glib::functions::markup_escape_text;
 
 // The TextBufferExt alias is necessary to avoid conflict with gtk's TextBufferExt
@@ -35,27 +35,25 @@ use std::time::Instant;
 pub struct Force(pub bool);
 
 impl AppOp {
-    pub fn remove_room(&mut self, id: String) {
+    pub fn remove_room(&mut self, id: RoomId) {
         self.rooms.remove(&id);
         self.unsent_messages.remove(&id);
         self.roomlist.remove_room(id);
     }
 
-    pub fn set_rooms(&mut self, mut rooms: Vec<Room>, clear_room_list: bool) {
+    pub fn set_rooms(&mut self, rooms: Vec<Room>, clear_room_list: bool) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
         if clear_room_list {
             self.rooms.clear();
         }
         let mut roomlist = vec![];
-        while let Some(room) = rooms.pop() {
-            if room.membership.is_left() {
-                // removing left rooms
-                if let RoomMembership::Left(kicked) = room.membership.clone() {
-                    if let Reason::Kicked(reason, kicker) = kicked {
-                        if let Some(r) = self.rooms.get(&room.id) {
-                            let room_name = r.name.clone().unwrap_or_default();
-                            self.kicked_room(room_name, reason, kicker.alias.unwrap_or_default());
-                        }
+        for room in rooms {
+            // removing left rooms
+            if let RoomMembership::Left(kicked) = room.membership.clone() {
+                if let Reason::Kicked(reason, kicker) = kicked {
+                    if let Some(r) = self.rooms.get(&room.id) {
+                        let room_name = r.name.clone().unwrap_or_default();
+                        self.kicked_room(room_name, reason, kicker.alias.unwrap_or_default());
                     }
                 }
                 if self.active_room.as_ref().map_or(false, |x| x == &room.id) {
@@ -63,9 +61,8 @@ impl AppOp {
                 } else {
                     self.remove_room(room.id);
                 }
-            } else if self.rooms.contains_key(&room.id) {
+            } else if let Some(update_room) = self.rooms.get_mut(&room.id) {
                 // TODO: update the existing rooms
-                let update_room = self.rooms.get_mut(&room.id).unwrap();
                 if room.language.is_some() {
                     update_room.language = room.language.clone();
                 };
@@ -156,7 +153,7 @@ impl AppOp {
         self.set_state(AppState::NoRoom);
     }
 
-    pub fn set_active_room_by_id(&mut self, id: String) {
+    pub fn set_active_room_by_id(&mut self, id: RoomId) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
         if let Some(room) = self.rooms.get(&id) {
             if let Some(language) = room.language.clone() {
@@ -280,9 +277,11 @@ impl AppOp {
         self.update_typing_notification();
     }
 
+    // FIXME: This should be a special case in a generic
+    //        function that leaves any room in any state.
     pub fn really_leave_active_room(&mut self) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
-        let r = self.active_room.clone().unwrap_or_default();
+        let r = unwrap_or_unit_return!(self.active_room.clone());
         self.backend
             .send(BKCommand::LeaveRoom(
                 login_data.server_url,
@@ -299,26 +298,24 @@ impl AppOp {
     }
 
     pub fn leave_active_room(&self) {
+        let active_room = unwrap_or_unit_return!(self.active_room.clone());
+        let r = unwrap_or_unit_return!(self.rooms.get(&active_room));
+
         let dialog = self
             .ui
             .builder
             .get_object::<gtk::MessageDialog>("leave_room_dialog")
             .expect("Can't find leave_room_dialog in ui file.");
 
-        if let Some(r) = self
-            .rooms
-            .get(&self.active_room.clone().unwrap_or_default())
-        {
-            let text = i18n_k(
-                "Leave {room_name}?",
-                &[("room_name", &r.name.clone().unwrap_or_default())],
-            );
-            dialog.set_property_text(Some(text.as_str()));
-            dialog.present();
-        }
+        let text = i18n_k(
+            "Leave {room_name}?",
+            &[("room_name", &r.name.clone().unwrap_or_default())],
+        );
+        dialog.set_property_text(Some(text.as_str()));
+        dialog.present();
     }
 
-    pub fn kicked_room(&self, roomid: String, reason: String, kicker: String) {
+    pub fn kicked_room(&self, room_name: String, reason: String, kicker: String) {
         let parent: gtk::Window = self
             .ui
             .builder
@@ -328,7 +325,7 @@ impl AppOp {
         let parent = upgrade_weak!(parent_weak);
         let viewer = widgets::KickedDialog::new();
         viewer.set_parent_window(&parent);
-        viewer.show(&roomid, &reason, &kicker);
+        viewer.show(&room_name, &reason, &kicker);
     }
 
     pub fn create_new_room(&mut self) {
@@ -354,7 +351,8 @@ impl AppOp {
             backend::RoomType::Public
         };
 
-        let internal_id: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
+        let internal_id = RoomId::new(&login_data.server_url.to_string())
+            .expect("The server domain should have been validated");
         self.backend
             .send(BKCommand::NewRoom(
                 login_data.server_url,
@@ -365,8 +363,11 @@ impl AppOp {
             ))
             .unwrap();
 
-        let mut fakeroom = Room::new(internal_id.clone(), RoomMembership::Joined(RoomTag::None));
-        fakeroom.name = Some(n);
+        let fakeroom = Room {
+            name: Some(n),
+            ..Room::new(internal_id.clone(), RoomMembership::Joined(RoomTag::None))
+        };
+
         self.new_room(fakeroom, None);
         self.set_active_room_by_id(internal_id);
         self.set_state(AppState::Room);
@@ -386,8 +387,8 @@ impl AppOp {
         };
     }
 
-    pub fn set_room_detail(&mut self, roomid: String, key: String, value: Option<String>) {
-        if let Some(r) = self.rooms.get_mut(&roomid) {
+    pub fn set_room_detail(&mut self, room_id: RoomId, key: String, value: Option<String>) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             let k: &str = &key;
             match k {
                 "m.room.name" => {
@@ -400,26 +401,30 @@ impl AppOp {
             };
         }
 
-        if roomid == self.active_room.clone().unwrap_or_default() {
+        if self
+            .active_room
+            .as_ref()
+            .map_or(false, |a_room| *a_room == room_id)
+        {
             self.set_current_room_detail(key, value);
         }
     }
 
-    pub fn set_room_avatar(&mut self, roomid: String, avatar: Option<Url>) {
+    pub fn set_room_avatar(&mut self, room_id: RoomId, avatar: Option<Url>) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
         if avatar.is_none() {
-            if let Ok(dest) = cache_dir_path(None, &roomid) {
+            if let Ok(dest) = cache_dir_path(None, &room_id.to_string()) {
                 let _ = remove_file(dest);
             }
         }
-        if let Some(r) = self.rooms.get_mut(&roomid) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             if avatar.is_none() && r.members.len() == 2 {
                 for m in r.members.keys() {
                     if *m != login_data.uid {
                         //FIXME: Find a better solution
                         // create a symlink from user avatar to room avatar (works only on unix)
                         if let Ok(source) = cache_dir_path(None, m) {
-                            if let Ok(dest) = cache_dir_path(None, &roomid) {
+                            if let Ok(dest) = cache_dir_path(None, &room_id.to_string()) {
                                 let _ = fs::symlink(source, dest);
                             }
                         }
@@ -428,7 +433,7 @@ impl AppOp {
             }
             r.avatar = avatar.map(|s| s.into_string());
             self.roomlist
-                .set_room_avatar(roomid.clone(), r.avatar.clone());
+                .set_room_avatar(room_id.clone(), r.avatar.clone());
         }
     }
 
@@ -490,24 +495,31 @@ impl AppOp {
             .ui
             .builder
             .get_object::<gtk::Entry>("join_room_name")
-            .expect("Can't find join_room_name in ui file.");
-
-        let n = name
+            .expect("Can't find join_room_name in ui file.")
             .get_text()
-            .map_or(String::new(), |gstr| gstr.to_string())
-            .trim()
-            .to_string();
+            .map_or(String::new(), |gstr| gstr.to_string());
 
-        self.backend
-            .send(BKCommand::JoinRoom(
-                login_data.server_url,
-                login_data.access_token,
-                n.clone(),
-            ))
-            .unwrap();
+        match RoomId::try_from(name.trim()) {
+            Ok(room_id) => {
+                self.backend
+                    .send(BKCommand::JoinRoom(
+                        login_data.server_url,
+                        login_data.access_token,
+                        room_id,
+                    ))
+                    .unwrap();
+            }
+            Err(err) => {
+                self.backend
+                    .send(BKCommand::SendBKResponse(BKResponse::JoinRoom(Err(
+                        err.into()
+                    ))))
+                    .unwrap();
+            }
+        }
     }
 
-    pub fn new_room(&mut self, r: Room, internal_id: Option<String>) {
+    pub fn new_room(&mut self, r: Room, internal_id: Option<RoomId>) {
         if let Some(id) = internal_id {
             self.remove_room(id);
         }
@@ -522,8 +534,8 @@ impl AppOp {
         self.set_active_room_by_id(r.id);
     }
 
-    pub fn added_to_fav(&mut self, roomid: String, tofav: bool) {
-        if let Some(ref mut r) = self.rooms.get_mut(&roomid) {
+    pub fn added_to_fav(&mut self, room_id: RoomId, tofav: bool) {
+        if let Some(ref mut r) = self.rooms.get_mut(&room_id) {
             let tag = if tofav {
                 RoomTag::Favourite
             } else {
@@ -536,60 +548,48 @@ impl AppOp {
     /// This method calculate the room name when there's no room name event
     /// For this we use the members in the room. If there's only one member we'll return that
     /// member name, if there's more than one we'll return the first one and others
-    pub fn recalculate_room_name(&mut self, roomid: String) {
+    pub fn recalculate_room_name(&mut self, room_id: RoomId) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
-        if !self.rooms.contains_key(&roomid) {
+        let r = unwrap_or_unit_return!(self.rooms.get_mut(&room_id));
+
+        // we should do nothing if this room has room name
+        if r.name.is_some() {
             return;
         }
 
-        let rname;
-        {
-            let r = self.rooms.get_mut(&roomid).unwrap();
-            // we should do nothing it this room has room name
-            if let Some(_) = r.name {
-                return;
-            }
-
-            // removing one because the user should be in the room
-            let n = r.members.len() - 1;
-            let suid = login_data.uid;
-            let mut members = r.members.iter().filter(|&(uid, _)| uid != &suid);
-
-            let m1 = match members.next() {
-                Some((_uid, m)) => m.get_alias(),
-                None => String::new(),
-            };
-
-            let m2 = match members.next() {
-                Some((_uid, m)) => m.get_alias(),
-                None => String::new(),
-            };
-
-            let name = match n {
-                0 => i18n("EMPTY ROOM"),
-                1 => String::from(m1),
-                2 => i18n_k("{m1} and {m2}", &[("m1", &m1), ("m2", &m2)]),
-                _ => i18n_k("{m1} and Others", &[("m1", &m1)]),
-            };
+        // removing one because the user should be in the room
+        let n = r.members.len() - 1;
+        let suid = login_data.uid;
+        let mut members = r
+            .members
+            .iter()
+            .filter(|&(uid, _)| uid != &suid)
+            .map(|(_uid, m)| m.get_alias());
+
+        let m1 = members.next().unwrap_or_default();
+        let m2 = members.next().unwrap_or_default();
+
+        let name = match n {
+            0 => i18n("EMPTY ROOM"),
+            1 => String::from(m1),
+            2 => i18n_k("{m1} and {m2}", &[("m1", &m1), ("m2", &m2)]),
+            _ => i18n_k("{m1} and Others", &[("m1", &m1)]),
+        };
 
-            r.name = Some(name);
-            rname = r.name.clone();
-        }
+        r.name = Some(name.clone());
 
-        self.room_name_change(roomid, rname);
+        self.room_name_change(room_id, Some(name));
     }
 
-    pub fn room_name_change(&mut self, roomid: String, name: Option<String>) {
-        if !self.rooms.contains_key(&roomid) {
-            return;
-        }
+    pub fn room_name_change(&mut self, room_id: RoomId, name: Option<String>) {
+        let r = unwrap_or_unit_return!(self.rooms.get_mut(&room_id));
+        r.name = name.clone();
 
+        if self
+            .active_room
+            .as_ref()
+            .map_or(false, |a_room| a_room == &room_id)
         {
-            let r = self.rooms.get_mut(&roomid).unwrap();
-            r.name = name.clone();
-        }
-
-        if roomid == self.active_room.clone().unwrap_or_default() {
             self.ui
                 .builder
                 .get_object::<gtk::Label>("room_name")
@@ -597,20 +597,18 @@ impl AppOp {
                 .set_text(&name.clone().unwrap_or_default());
         }
 
-        self.roomlist.rename_room(roomid.clone(), name);
+        self.roomlist.rename_room(room_id, name);
     }
 
-    pub fn room_topic_change(&mut self, roomid: String, topic: Option<String>) {
-        if !self.rooms.contains_key(&roomid) {
-            return;
-        }
+    pub fn room_topic_change(&mut self, room_id: RoomId, topic: Option<String>) {
+        let r = unwrap_or_unit_return!(self.rooms.get_mut(&room_id));
+        r.topic = topic.clone();
 
+        if self
+            .active_room
+            .as_ref()
+            .map_or(false, |a_room| *a_room == room_id)
         {
-            let r = self.rooms.get_mut(&roomid).unwrap();
-            r.topic = topic.clone();
-        }
-
-        if roomid == self.active_room.clone().unwrap_or_default() {
             self.set_room_topic_label(topic);
         }
     }
@@ -647,9 +645,9 @@ impl AppOp {
         };
     }
 
-    pub fn new_room_avatar(&self, roomid: String) {
+    pub fn new_room_avatar(&self, room_id: RoomId) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
-        if !self.rooms.contains_key(&roomid) {
+        if !self.rooms.contains_key(&room_id) {
             return;
         }
 
@@ -657,62 +655,59 @@ impl AppOp {
             .send(BKCommand::GetRoomAvatar(
                 login_data.server_url,
                 login_data.access_token,
-                roomid,
+                room_id,
             ))
             .unwrap();
     }
 
     pub fn update_typing_notification(&mut self) {
-        if let Some(active_room) = &self
-            .rooms
-            .get(&self.active_room.clone().unwrap_or_default())
-        {
-            if let Some(ref mut history) = self.history {
-                let typing_users = &active_room.typing_users;
-                if typing_users.len() == 0 {
-                    history.typing_notification("");
-                } else if typing_users.len() > 2 {
-                    history.typing_notification(&i18n("Several users are typing…"));
-                } else {
-                    let typing_string = ni18n_f(
-                        "<b>{}</b> is typing…",
-                        "<b>{}</b> and <b>{}</b> are typing…",
-                        typing_users.len() as u32,
-                        typing_users
-                            .iter()
-                            .map(|user| markup_escape_text(&user.get_alias()).to_string())
-                            .collect::<Vec<String>>()
-                            .iter()
-                            .map(std::ops::Deref::deref)
-                            .collect::<Vec<&str>>()
-                            .as_slice(),
-                    );
-                    history.typing_notification(&typing_string);
-                }
-            }
+        let active_room_id = unwrap_or_unit_return!(self.active_room.clone());
+        let active_room = unwrap_or_unit_return!(self.rooms.get(&active_room_id));
+        let history = unwrap_or_unit_return!(self.history.as_mut());
+
+        let typing_users = &active_room.typing_users;
+        if typing_users.len() == 0 {
+            history.typing_notification("");
+        } else if typing_users.len() > 2 {
+            history.typing_notification(&i18n("Several users are typing…"));
+        } else {
+            let typing_string = ni18n_f(
+                "<b>{}</b> is typing…",
+                "<b>{}</b> and <b>{}</b> are typing…",
+                typing_users.len() as u32,
+                typing_users
+                    .iter()
+                    .map(|user| markup_escape_text(&user.get_alias()).to_string())
+                    .collect::<Vec<String>>()
+                    .iter()
+                    .map(std::ops::Deref::deref)
+                    .collect::<Vec<&str>>()
+                    .as_slice(),
+            );
+            history.typing_notification(&typing_string);
         }
     }
 
     pub fn send_typing(&mut self) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
-        if let Some(ref active_room) = self.active_room {
-            let now = Instant::now();
-            if let Some(last_typing) = self.typing.get(active_room) {
-                let time_passed = now.duration_since(*last_typing);
-                if time_passed.as_secs() < 3 {
-                    return;
-                }
+        let active_room = unwrap_or_unit_return!(self.active_room.as_ref());
+
+        let now = Instant::now();
+        if let Some(last_typing) = self.typing.get(active_room) {
+            let time_passed = now.duration_since(*last_typing);
+            if time_passed.as_secs() < 3 {
+                return;
             }
-            self.typing.insert(active_room.clone(), now);
-            self.backend
-                .send(BKCommand::SendTyping(
-                    login_data.server_url,
-                    login_data.access_token,
-                    login_data.uid,
-                    active_room.clone(),
-                ))
-                .unwrap();
         }
+        self.typing.insert(active_room.clone(), now);
+        self.backend
+            .send(BKCommand::SendTyping(
+                login_data.server_url,
+                login_data.access_token,
+                login_data.uid,
+                active_room.clone(),
+            ))
+            .unwrap();
     }
 
     pub fn set_language(&self, lang_code: String) {
diff --git a/fractal-gtk/src/appop/start_chat.rs b/fractal-gtk/src/appop/start_chat.rs
index f4edc7d1..3cba7936 100644
--- a/fractal-gtk/src/appop/start_chat.rs
+++ b/fractal-gtk/src/appop/start_chat.rs
@@ -1,3 +1,4 @@
+use fractal_api::identifiers::RoomId;
 use gtk;
 use gtk::prelude::*;
 
@@ -8,9 +9,6 @@ use crate::appop::SearchType;
 use crate::backend::BKCommand;
 use crate::types::{Room, RoomMembership, RoomTag};
 
-use rand::distributions::Alphanumeric;
-use rand::{thread_rng, Rng};
-
 impl AppOp {
     pub fn start_chat(&mut self) {
         if self.invite_list.len() != 1 {
@@ -20,7 +18,8 @@ impl AppOp {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
         let user = self.invite_list[0].clone();
 
-        let internal_id: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
+        let internal_id = RoomId::new(&login_data.server_url.to_string())
+            .expect("The server domain should have been validated");
         self.backend
             .send(BKCommand::DirectChat(
                 login_data.server_url,
@@ -32,9 +31,11 @@ impl AppOp {
             .unwrap();
         self.close_direct_chat_dialog();
 
-        let mut fakeroom = Room::new(internal_id.clone(), RoomMembership::Joined(RoomTag::None));
-        fakeroom.name = user.0.alias;
-        fakeroom.direct = true;
+        let fakeroom = Room {
+            name: user.0.alias,
+            direct: true,
+            ..Room::new(internal_id.clone(), RoomMembership::Joined(RoomTag::None))
+        };
 
         self.new_room(fakeroom, None);
         self.set_active_room_by_id(internal_id);
diff --git a/fractal-gtk/src/appop/state.rs b/fractal-gtk/src/appop/state.rs
index 7c9699ea..b08fb9b6 100644
--- a/fractal-gtk/src/appop/state.rs
+++ b/fractal-gtk/src/appop/state.rs
@@ -89,10 +89,10 @@ impl AppOp {
 
         self.ui.sventry.view.grab_focus();
 
-        let active_room_id = self.active_room.clone().unwrap_or_default();
         let msg = self
-            .unsent_messages
-            .get(&active_room_id)
+            .active_room
+            .as_ref()
+            .and_then(|active_room_id| self.unsent_messages.get(active_room_id))
             .cloned()
             .unwrap_or_default();
         if let Some(buffer) = self.ui.sventry.view.get_buffer() {
diff --git a/fractal-gtk/src/widgets/room.rs b/fractal-gtk/src/widgets/room.rs
index 109f9ba3..daac0982 100644
--- a/fractal-gtk/src/widgets/room.rs
+++ b/fractal-gtk/src/widgets/room.rs
@@ -48,7 +48,13 @@ impl<'a> RoomBox<'a> {
             let room = self.room;
 
             let avatar = widgets::Avatar::avatar_new(Some(AVATAR_SIZE));
-            avatar.circle(room.id.clone(), room.name.clone(), AVATAR_SIZE, None, None);
+            avatar.circle(
+                room.id.to_string(),
+                room.name.clone(),
+                AVATAR_SIZE,
+                None,
+                None,
+            );
             widget_box.pack_start(&avatar, false, false, 18);
 
             let details_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index 12cbe094..3f8b32a4 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -18,6 +18,7 @@ use crate::uitypes::RowType;
 use crate::globals;
 use crate::widgets;
 use crate::widgets::{PlayerExt, VideoPlayerWidget};
+use fractal_api::identifiers::RoomId;
 use fractal_api::url::Url;
 use gio::ActionMapExt;
 use gio::SimpleActionGroup;
@@ -259,7 +260,7 @@ pub struct RoomHistory {
 }
 
 impl RoomHistory {
-    pub fn new(actions: SimpleActionGroup, room_id: String, op: &AppOp) -> Option<RoomHistory> {
+    pub fn new(actions: SimpleActionGroup, room_id: RoomId, op: &AppOp) -> Option<RoomHistory> {
         let history_container = op
             .ui
             .builder
diff --git a/fractal-gtk/src/widgets/room_settings.rs b/fractal-gtk/src/widgets/room_settings.rs
index 10891fe0..8f10f602 100644
--- a/fractal-gtk/src/widgets/room_settings.rs
+++ b/fractal-gtk/src/widgets/room_settings.rs
@@ -168,7 +168,7 @@ impl RoomSettings {
 
         if let Some(action) = self.actions.lookup_action("change-avatar") {
             action.bind_button_state(&avatar_btn);
-            let data = glib::Variant::from(&self.room.id);
+            let data = glib::Variant::from(&self.room.id.to_string());
             avatar_btn.set_action_target_value(Some(&data));
             avatar_btn.set_action_name(Some("room-settings.change-avatar"));
             let avatar_spinner = self
@@ -428,7 +428,7 @@ impl RoomSettings {
 
         let image = widgets::Avatar::avatar_new(Some(100));
         let data = image.circle(
-            self.room.id.clone(),
+            self.room.id.to_string(),
             self.room.name.clone(),
             100,
             None,
@@ -437,7 +437,7 @@ impl RoomSettings {
         download_to_cache(
             self.backend.clone(),
             self.server_url.clone(),
-            self.room.id.clone(),
+            self.room.id.to_string(),
             data,
         );
 
diff --git a/fractal-gtk/src/widgets/roomlist.rs b/fractal-gtk/src/widgets/roomlist.rs
index 86d637af..2d118cec 100644
--- a/fractal-gtk/src/widgets/roomlist.rs
+++ b/fractal-gtk/src/widgets/roomlist.rs
@@ -1,5 +1,6 @@
 use crate::i18n::i18n;
 use fractal_api::clone;
+use fractal_api::identifiers::RoomId;
 
 use gdk;
 use glib;
@@ -13,6 +14,7 @@ use std::collections::HashMap;
 use crate::globals;
 use crate::types::{Room, RoomTag};
 use crate::widgets::roomrow::RoomRow;
+use std::convert::TryFrom;
 use std::sync::{Arc, Mutex, MutexGuard};
 
 use chrono::prelude::*;
@@ -45,7 +47,7 @@ pub enum RoomListType {
 }
 
 pub struct RoomListGroup {
-    pub rooms: HashMap<String, RoomRow>,
+    pub rooms: HashMap<RoomId, RoomRow>,
     pub baseu: Url,
     pub list: gtk::ListBox,
     rev: gtk::Revealer,
@@ -173,27 +175,27 @@ impl RoomListGroup {
         self.show();
     }
 
-    pub fn set_bold(&mut self, room: String, bold: bool) {
-        if let Some(ref mut r) = self.rooms.get_mut(&room) {
+    pub fn set_bold(&mut self, room_id: RoomId, bold: bool) {
+        if let Some(ref mut r) = self.rooms.get_mut(&room_id) {
             r.set_bold(bold);
         }
     }
 
-    pub fn set_room_notifications(&mut self, room: String, n: i32, h: i32) {
-        if let Some(ref mut r) = self.rooms.get_mut(&room) {
+    pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+        if let Some(ref mut r) = self.rooms.get_mut(&room_id) {
             r.set_notifications(n, h);
         }
 
-        self.edit_room(&room, move |rv| {
+        self.edit_room(&room_id, move |rv| {
             rv.room.notifications = n;
             rv.room.highlight = h;
         });
     }
 
-    pub fn remove_room(&mut self, room: String) -> Option<RoomUpdated> {
-        self.rooms.remove(&room);
+    pub fn remove_room(&mut self, room_id: RoomId) -> Option<RoomUpdated> {
+        self.rooms.remove(&room_id);
         let mut rv = self.roomvec.lock().unwrap();
-        if let Some(idx) = rv.iter().position(|x| x.room.id == room) {
+        if let Some(idx) = rv.iter().position(|x| x.room.id == room_id) {
             if let Some(row) = self.list.get_row_at_index(idx as i32) {
                 self.list.remove(&row);
             }
@@ -204,22 +206,22 @@ impl RoomListGroup {
         None
     }
 
-    pub fn rename_room(&mut self, room: String, newname: Option<String>) {
-        if let (Some(r), Some(n)) = (self.rooms.get_mut(&room), newname.clone()) {
+    pub fn rename_room(&mut self, room_id: RoomId, newname: Option<String>) {
+        if let (Some(r), Some(n)) = (self.rooms.get_mut(&room_id), newname.clone()) {
             r.set_name(n);
         }
 
-        self.edit_room(&room, move |rv| {
+        self.edit_room(&room_id, move |rv| {
             rv.room.name = newname.clone();
         });
     }
 
-    pub fn set_room_avatar(&mut self, room: String, av: Option<String>) {
-        if let Some(r) = self.rooms.get_mut(&room) {
+    pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<String>) {
+        if let Some(r) = self.rooms.get_mut(&room_id) {
             r.set_avatar(av.clone());
         }
 
-        self.edit_room(&room, move |rv| {
+        self.edit_room(&room_id, move |rv| {
             rv.room.avatar = av.clone();
         });
     }
@@ -271,27 +273,22 @@ impl RoomListGroup {
         self.widget.hide();
     }
 
-    pub fn get_selected(&self) -> Option<String> {
+    pub fn get_selected(&self) -> Option<RoomId> {
         let rv = self.roomvec.lock().unwrap();
-        match self.list.get_selected_row() {
-            Some(row) => Some(rv[row.get_index() as usize].room.id.clone()),
-            None => None,
-        }
+        self.list
+            .get_selected_row()
+            .map(|row| rv[row.get_index() as usize].room.id.clone())
     }
 
-    pub fn set_selected(&self, room: Option<String>) {
+    pub fn set_selected(&self, room_id: Option<RoomId>) {
         self.list.unselect_all();
 
-        if room.is_none() {
-            return;
-        }
-
-        let room = room.unwrap();
-
-        let rv = self.roomvec.lock().unwrap();
-        if let Some(idx) = rv.iter().position(|x| x.room.id == room) {
-            if let Some(ref row) = self.list.get_row_at_index(idx as i32) {
-                self.list.select_row(Some(row));
+        if let Some(room_id) = room_id {
+            let rv = self.roomvec.lock().unwrap();
+            if let Some(idx) = rv.iter().position(|x| x.room.id == room_id) {
+                if let Some(ref row) = self.list.get_row_at_index(idx as i32) {
+                    self.list.select_row(Some(row));
+                }
             }
         }
     }
@@ -306,20 +303,20 @@ impl RoomListGroup {
     /// # Return value
     ///
     /// `(Room id if found, go to previous group, go to next group)`
-    fn sibling_id(&self, unread_only: bool, direction: i32) -> (Option<String>, bool, bool) {
+    fn sibling_id(&self, unread_only: bool, direction: i32) -> (Option<RoomId>, bool, bool) {
         match self.list.get_selected_row() {
             Some(row) => {
                 let rv = self.roomvec.lock().unwrap();
                 let mut idx = row.get_index() + direction;
                 while unread_only
-                    && 0 <= idx
+                    && idx >= 0
                     && (idx as usize) < rv.len()
                     && rv[idx as usize].room.notifications == 0
                 {
                     idx += direction;
                 }
 
-                if 0 <= idx && (idx as usize) < rv.len() {
+                if idx >= 0 && (idx as usize) < rv.len() {
                     (Some(rv[idx as usize].room.id.clone()), false, false)
                 } else {
                     (None, idx < 0, idx >= 0)
@@ -329,7 +326,7 @@ impl RoomListGroup {
         }
     }
 
-    fn first_id(&self, unread_only: bool) -> Option<String> {
+    fn first_id(&self, unread_only: bool) -> Option<RoomId> {
         self.roomvec
             .lock()
             .unwrap()
@@ -345,7 +342,7 @@ impl RoomListGroup {
             .map(|r| r.room.id.clone())
     }
 
-    fn last_id(&self, unread_only: bool) -> Option<String> {
+    fn last_id(&self, unread_only: bool) -> Option<RoomId> {
         self.roomvec
             .lock()
             .unwrap()
@@ -372,13 +369,13 @@ impl RoomListGroup {
         }
     }
 
-    pub fn moveup(&mut self, room: String) {
+    pub fn moveup(&mut self, room_id: RoomId) {
         let s = self.get_selected();
 
-        self.edit_room(&room, move |rv| {
+        self.edit_room(&room_id, move |rv| {
             rv.up();
         });
-        if let Some(r) = self.remove_room(room) {
+        if let Some(r) = self.remove_room(room_id) {
             self.add_room_up(r);
         }
 
@@ -393,9 +390,9 @@ impl RoomListGroup {
         }
     }
 
-    fn edit_room<F: Fn(&mut RoomUpdated) + 'static>(&mut self, room: &str, cb: F) {
+    fn edit_room<F: Fn(&mut RoomUpdated) + 'static>(&mut self, room_id: &RoomId, cb: F) {
         let mut rv = self.roomvec.lock().unwrap();
-        if let Some(idx) = rv.iter().position(|x| x.room.id == room) {
+        if let Some(idx) = rv.iter().position(|x| x.room.id == *room_id) {
             if let Some(ref mut m) = rv.get_mut(idx) {
                 cb(m);
             }
@@ -454,10 +451,10 @@ pub struct RoomList {
 }
 
 macro_rules! run_in_group {
-    ($self: expr, $roomid: expr, $fn: ident, $($arg: expr),*) => {{
-        if $self.inv.get().rooms.contains_key($roomid) {
+    ($self: expr, $room_id: expr, $fn: ident, $($arg: expr),*) => {{
+        if $self.inv.get().rooms.contains_key($room_id) {
             $self.inv.get().$fn($($arg),*)
-        } else if $self.fav.get().rooms.contains_key($roomid) {
+        } else if $self.fav.get().rooms.contains_key($room_id) {
             $self.fav.get().$fn($($arg),*)
         } else {
             $self.rooms.get().$fn($($arg),*)
@@ -498,16 +495,15 @@ impl RoomList {
         rl
     }
 
-    pub fn select(&self, r: &str) {
-        //FIXME don't use to_string(), pass &str
-        run_in_group!(self, &r.to_string(), set_selected, Some(r.to_string()));
+    pub fn select(&self, room_id: &RoomId) {
+        run_in_group!(self, room_id, set_selected, Some(room_id.clone()));
     }
 
-    fn sibling_id_inv(&self, unread_only: bool, direction: i32) -> Option<String> {
-        let (room, _, next) = self.inv.get().sibling_id(unread_only, direction);
+    fn sibling_id_inv(&self, unread_only: bool, direction: i32) -> Option<RoomId> {
+        let (room_id, _, next) = self.inv.get().sibling_id(unread_only, direction);
 
-        if let Some(room) = room {
-            Some(room)
+        if let Some(room_id) = room_id {
+            Some(room_id)
         } else if next {
             self.fav.get().first_id(unread_only)
         } else {
@@ -515,11 +511,11 @@ impl RoomList {
         }
     }
 
-    fn sibling_id_fav(&self, unread_only: bool, direction: i32) -> Option<String> {
-        let (room, prev, next) = self.fav.get().sibling_id(unread_only, direction);
+    fn sibling_id_fav(&self, unread_only: bool, direction: i32) -> Option<RoomId> {
+        let (room_id, prev, next) = self.fav.get().sibling_id(unread_only, direction);
 
-        if let Some(room) = room {
-            Some(room)
+        if let Some(room_id) = room_id {
+            Some(room_id)
         } else if prev {
             self.inv.get().last_id(unread_only)
         } else if next {
@@ -529,11 +525,11 @@ impl RoomList {
         }
     }
 
-    fn sibling_id_rooms(&self, unread_only: bool, direction: i32) -> Option<String> {
-        let (room, prev, _) = self.rooms.get().sibling_id(unread_only, direction);
+    fn sibling_id_rooms(&self, unread_only: bool, direction: i32) -> Option<RoomId> {
+        let (room_id, prev, _) = self.rooms.get().sibling_id(unread_only, direction);
 
-        if let Some(room) = room {
-            Some(room)
+        if let Some(room_id) = room_id {
+            Some(room_id)
         } else if prev {
             self.fav.get().last_id(unread_only)
         } else {
@@ -541,27 +537,27 @@ impl RoomList {
         }
     }
 
-    fn sibling_id(&self, unread_only: bool, direction: i32) -> Option<String> {
+    fn sibling_id(&self, unread_only: bool, direction: i32) -> Option<RoomId> {
         self.sibling_id_inv(unread_only, direction)
     }
 
-    pub fn next_id(&self) -> Option<String> {
+    pub fn next_id(&self) -> Option<RoomId> {
         self.sibling_id(false, 1)
     }
 
-    pub fn prev_id(&self) -> Option<String> {
+    pub fn prev_id(&self) -> Option<RoomId> {
         self.sibling_id(false, -1)
     }
 
-    pub fn next_unread_id(&self) -> Option<String> {
+    pub fn next_unread_id(&self) -> Option<RoomId> {
         self.sibling_id(true, 1)
     }
 
-    pub fn prev_unread_id(&self) -> Option<String> {
+    pub fn prev_unread_id(&self) -> Option<RoomId> {
         self.sibling_id(true, -1)
     }
 
-    pub fn first_id(&self) -> Option<String> {
+    pub fn first_id(&self) -> Option<RoomId> {
         self.inv
             .get()
             .first_id(false)
@@ -569,7 +565,7 @@ impl RoomList {
             .or_else(|| self.rooms.get().first_id(false))
     }
 
-    pub fn last_id(&self) -> Option<String> {
+    pub fn last_id(&self) -> Option<RoomId> {
         self.rooms
             .get()
             .last_id(false)
@@ -615,8 +611,8 @@ impl RoomList {
         let r = self.rooms.clone();
         let f = self.fav.clone();
         let cb = acb.clone();
-        self.connect_drop(favw, move |roomid| {
-            if let Some(room) = r.get().remove_room(roomid) {
+        self.connect_drop(favw, move |room_id| {
+            if let Some(room) = r.get().remove_room(room_id) {
                 cb(room.room.clone(), true);
                 f.get().add_room_up(room);
             }
@@ -634,22 +630,22 @@ impl RoomList {
         });
     }
 
-    pub fn set_room_avatar(&mut self, room: String, av: Option<String>) {
-        run_in_group!(self, &room, set_room_avatar, room, av);
+    pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<String>) {
+        run_in_group!(self, &room_id, set_room_avatar, room_id, av);
     }
 
-    pub fn set_room_notifications(&mut self, room: String, n: i32, h: i32) {
-        run_in_group!(self, &room, set_room_notifications, room, n, h);
+    pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+        run_in_group!(self, &room_id, set_room_notifications, room_id, n, h);
     }
 
-    pub fn remove_room(&mut self, room: String) -> Option<RoomUpdated> {
-        let ret = run_in_group!(self, &room, remove_room, room);
+    pub fn remove_room(&mut self, room_id: RoomId) -> Option<RoomUpdated> {
+        let ret = run_in_group!(self, &room_id, remove_room, room_id);
         self.show_and_hide();
         ret
     }
 
-    pub fn set_bold(&mut self, room: String, bold: bool) {
-        run_in_group!(self, &room, set_bold, room, bold)
+    pub fn set_bold(&mut self, room_id: RoomId, bold: bool) {
+        run_in_group!(self, &room_id, set_bold, room_id, bold)
     }
 
     pub fn add_room(&mut self, r: Room) {
@@ -665,12 +661,12 @@ impl RoomList {
         self.show_and_hide();
     }
 
-    pub fn rename_room(&mut self, room: String, newname: Option<String>) {
-        run_in_group!(self, &room, rename_room, room, newname);
+    pub fn rename_room(&mut self, room_id: RoomId, newname: Option<String>) {
+        run_in_group!(self, &room_id, rename_room, room_id, newname);
     }
 
-    pub fn moveup(&mut self, room: String) {
-        run_in_group!(self, &room, moveup, room);
+    pub fn moveup(&mut self, room_id: RoomId) {
+        run_in_group!(self, &room_id, moveup, room_id);
     }
 
     // Roomlist widget
@@ -732,7 +728,7 @@ impl RoomList {
         });
     }
 
-    pub fn connect_drop<F: Fn(String) + 'static>(&self, widget: gtk::EventBox, cb: F) {
+    pub fn connect_drop<F: Fn(RoomId) + 'static>(&self, widget: gtk::EventBox, cb: F) {
         let flags = gtk::DestDefaults::empty();
         let action = gdk::DragAction::all();
         widget.drag_dest_set(flags, &[], action);
@@ -748,8 +744,11 @@ impl RoomList {
             glib::signal::Inhibit(true)
         });
         widget.connect_drag_data_received(move |_w, _ctx, _x, _y, data, _info, _time| {
-            if let Some(roomid) = data.get_text() {
-                cb(roomid.to_string());
+            if let Some(room_id) = data
+                .get_text()
+                .and_then(|rid| RoomId::try_from(rid.as_str()).ok())
+            {
+                cb(room_id);
             }
         });
     }
diff --git a/fractal-gtk/src/widgets/roomrow.rs b/fractal-gtk/src/widgets/roomrow.rs
index e5cf62ed..f0907e65 100644
--- a/fractal-gtk/src/widgets/roomrow.rs
+++ b/fractal-gtk/src/widgets/roomrow.rs
@@ -63,7 +63,7 @@ impl RoomRow {
             notifications.hide();
         }
 
-        icon.circle(room.id.clone(), Some(name), ICON_SIZE, None, None);
+        icon.circle(room.id.to_string(), Some(name), ICON_SIZE, None, None);
 
         let rr = RoomRow {
             room,
@@ -126,7 +126,7 @@ impl RoomRow {
         let name = self.room.name.clone().unwrap_or("...".to_string());
 
         self.icon
-            .circle(self.room.id.clone(), Some(name), ICON_SIZE, None, None);
+            .circle(self.room.id.to_string(), Some(name), ICON_SIZE, None, None);
     }
 
     pub fn widget(&self) -> gtk::ListBoxRow {
@@ -155,7 +155,7 @@ impl RoomRow {
 
         let row = gtk::ListBoxRow::new();
         row.add(&self.widget);
-        let data = glib::Variant::from(&self.room.id);
+        let data = glib::Variant::from(&self.room.id.to_string());
         row.set_action_target_value(Some(&data));
         row.set_action_name(Some("app.open-room"));
 
@@ -186,7 +186,7 @@ impl RoomRow {
             ctx.drag_set_icon_surface(&image);
         });
 
-        let id = self.room.id.clone();
+        let id = self.room.id.to_string();
         self.widget
             .connect_drag_data_get(move |_w, _, data, _x, _y| {
                 data.set_text(&id);
diff --git a/fractal-gtk/src/widgets/scroll_widget.rs b/fractal-gtk/src/widgets/scroll_widget.rs
index 08c5090e..bb9233c5 100644
--- a/fractal-gtk/src/widgets/scroll_widget.rs
+++ b/fractal-gtk/src/widgets/scroll_widget.rs
@@ -1,6 +1,7 @@
 use std::cell::Cell;
 use std::rc::Rc;
 
+use fractal_api::identifiers::RoomId;
 use gio::Action;
 use gio::ActionExt;
 use gtk;
@@ -117,7 +118,7 @@ impl Widgets {
 }
 
 impl ScrollWidget {
-    pub fn new(action: Option<Action>, room_id: String) -> ScrollWidget {
+    pub fn new(action: Option<Action>, room_id: RoomId) -> ScrollWidget {
         let builder = gtk::Builder::new();
 
         builder
@@ -149,7 +150,7 @@ impl ScrollWidget {
     }
 
     /* Keep the same position if new messages are added */
-    pub fn connect(&mut self, action: Option<Action>, room_id: String) -> Option<()> {
+    pub fn connect(&mut self, action: Option<Action>, room_id: RoomId) -> Option<()> {
         let adj = self.widgets.view.get_vadjustment()?;
         let upper = Rc::downgrade(&self.upper);
         let balance = Rc::downgrade(&self.balance);
@@ -217,7 +218,7 @@ impl ScrollWidget {
                     if adj.get_value() < adj.get_page_size() * 2.0 {
                         /* Load more messages once the user is nearly at the end of the history */
                         spinner.start();
-                        let data = glib::Variant::from(&room_id);
+                        let data = glib::Variant::from(&room_id.to_string());
                         action.activate(Some(&data));
                         request_sent.set(true);
                     }
diff --git a/fractal-matrix-api/src/backend/directory.rs b/fractal-matrix-api/src/backend/directory.rs
index 675bf519..f9796b71 100644
--- a/fractal-matrix-api/src/backend/directory.rs
+++ b/fractal-matrix-api/src/backend/directory.rs
@@ -114,7 +114,7 @@ pub fn room_search(
                     .map(Into::into)
                     .inspect(|r: &Room| {
                         if let Some(avatar) = r.avatar.clone() {
-                            if let Ok(dest) = cache_dir_path(None, &r.id) {
+                            if let Ok(dest) = cache_dir_path(None, &r.id.to_string()) {
                                 let _ =
                                     dw_media(&base, &avatar, ContentType::Download, Some(&dest));
                             }
diff --git a/fractal-matrix-api/src/backend/media.rs b/fractal-matrix-api/src/backend/media.rs
index 8a5f56b5..83d3ce3c 100644
--- a/fractal-matrix-api/src/backend/media.rs
+++ b/fractal-matrix-api/src/backend/media.rs
@@ -1,6 +1,7 @@
 use crate::backend::types::Backend;
 use crate::error::Error;
 use crate::globals;
+use ruma_identifiers::RoomId;
 use serde_json::json;
 use std::str::Split;
 use std::sync::mpsc::Sender;
@@ -39,7 +40,7 @@ pub fn get_media_list_async(
     bk: &Backend,
     baseu: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     first_media_id: Option<String>,
     prev_batch: Option<String>,
     tx: Sender<(Vec<Message>, String)>,
@@ -48,7 +49,7 @@ pub fn get_media_list_async(
         let media_list = get_room_media_list(
             &baseu,
             &access_token,
-            &roomid,
+            &room_id,
             globals::PAGE_LIMIT,
             first_media_id,
             &prev_batch,
@@ -76,7 +77,7 @@ pub fn get_file_async(url: Url, tx: Sender<String>) -> Result<(), Error> {
 fn get_room_media_list(
     baseu: &Url,
     tk: &AccessToken,
-    roomid: &str,
+    room_id: &RoomId,
     limit: i32,
     first_media_id: Option<String>,
     prev_batch: &Option<String>,
@@ -100,12 +101,12 @@ fn get_room_media_list(
         Some(ref pb) => params.push(("from", pb.clone())),
         None => {
             if let Some(id) = first_media_id {
-                params.push(("from", get_prev_batch_from(baseu, tk, &roomid, &id)?))
+                params.push(("from", get_prev_batch_from(baseu, tk, room_id, &id)?))
             }
         }
     };
 
-    let path = format!("rooms/{}/messages", roomid);
+    let path = format!("rooms/{}/messages", room_id);
     let url = client_url(baseu, &path, &params)?;
 
     let r = json_q("get", url, &json!(null))?;
@@ -116,7 +117,7 @@ fn get_room_media_list(
     }
 
     let evs = array.unwrap().iter().rev();
-    let media_list = Message::from_json_events_iter(roomid, evs);
+    let media_list = Message::from_json_events_iter(room_id, evs);
 
     Ok((media_list, prev_batch))
 }
diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs
index 92b2c020..02aefcd9 100644
--- a/fractal-matrix-api/src/backend/mod.rs
+++ b/fractal-matrix-api/src/backend/mod.rs
@@ -37,7 +37,7 @@ impl Backend {
     pub fn new(tx: Sender<BKResponse>) -> Backend {
         let data = BackendData {
             rooms_since: String::new(),
-            join_to_room: String::new(),
+            join_to_room: None,
             m_direct: HashMap::new(),
         };
         Backend {
@@ -221,16 +221,19 @@ impl Backend {
             }
 
             // Room module
-            Ok(BKCommand::GetRoomMembers(server, access_token, room)) => {
-                let r = room::get_room_members(self, server, access_token, room);
-                bkerror2!(r, tx, BKResponse::RoomMembers);
+            Ok(BKCommand::GetRoomMembers(server, access_token, room_id)) => {
+                thread::spawn(move || {
+                    let query = room::get_room_members(server, access_token, room_id);
+                    tx.send(BKResponse::RoomMembers(query))
+                        .expect_log("Connection closed");
+                });
             }
-            Ok(BKCommand::GetRoomMessages(server, access_token, room, from)) => {
-                let r = room::get_room_messages(self, server, access_token, room, from);
+            Ok(BKCommand::GetRoomMessages(server, access_token, room_id, from)) => {
+                let r = room::get_room_messages(self, server, access_token, room_id, from);
                 bkerror2!(r, tx, BKResponse::RoomMessagesTo);
             }
-            Ok(BKCommand::GetRoomMessagesFromMsg(server, access_token, room, from)) => {
-                room::get_room_messages_from_msg(self, server, access_token, room, from)
+            Ok(BKCommand::GetRoomMessagesFromMsg(server, access_token, room_id, from)) => {
+                room::get_room_messages_from_msg(self, server, access_token, room_id, from)
             }
             Ok(BKCommand::GetMessageContext(server, access_token, message)) => {
                 let r = room::get_message_context(self, server, access_token, message);
@@ -244,86 +247,103 @@ impl Backend {
                 let r = room::redact_msg(self, server, access_token, &msg);
                 bkerror2!(r, tx, BKResponse::SentMsgRedaction);
             }
-            Ok(BKCommand::SendTyping(server, access_token, uid, room)) => {
-                let r = room::send_typing(self, server, access_token, uid, room);
-                bkerror!(r, tx, BKResponse::SendTypingError);
+            Ok(BKCommand::SendTyping(server, access_token, uid, room_id)) => {
+                thread::spawn(move || {
+                    let query = room::send_typing(server, access_token, uid, room_id);
+                    if let Err(err) = query {
+                        tx.send(BKResponse::SendTypingError(err))
+                            .expect_log("Connection closed");
+                    }
+                });
             }
-            Ok(BKCommand::SetRoom(server, access_token, id)) => {
-                let r = room::set_room(self, server, access_token, id);
+            Ok(BKCommand::SetRoom(server, access_token, room_id)) => {
+                let r = room::set_room(self, server, access_token, room_id);
                 bkerror!(r, tx, BKResponse::SetRoomError);
             }
-            Ok(BKCommand::GetRoomAvatar(server, access_token, room)) => {
-                let r = room::get_room_avatar(self, server, access_token, room);
+            Ok(BKCommand::GetRoomAvatar(server, access_token, room_id)) => {
+                let r = room::get_room_avatar(self, server, access_token, room_id);
                 bkerror2!(r, tx, BKResponse::RoomAvatar);
             }
-            Ok(BKCommand::JoinRoom(server, access_token, roomid)) => {
-                let r = room::join_room(self, server, access_token, roomid);
-                bkerror2!(r, tx, BKResponse::JoinRoom);
+            Ok(BKCommand::JoinRoom(server, access_token, room_id)) => {
+                room::join_room(self, server, access_token, room_id)
             }
-            Ok(BKCommand::LeaveRoom(server, access_token, roomid)) => {
-                let r = room::leave_room(self, server, access_token, roomid);
-                bkerror2!(r, tx, BKResponse::LeaveRoom);
+            Ok(BKCommand::LeaveRoom(server, access_token, room_id)) => {
+                thread::spawn(move || {
+                    let query = room::leave_room(server, access_token, room_id);
+                    tx.send(BKResponse::LeaveRoom(query))
+                        .expect_log("Connection closed");
+                });
             }
-            Ok(BKCommand::MarkAsRead(server, access_token, roomid, evid)) => {
-                let r = room::mark_as_read(self, server, access_token, roomid, evid);
+            Ok(BKCommand::MarkAsRead(server, access_token, room_id, evid)) => {
+                let r = room::mark_as_read(self, server, access_token, room_id, evid);
                 bkerror2!(r, tx, BKResponse::MarkedAsRead);
             }
-            Ok(BKCommand::SetRoomName(server, access_token, roomid, name)) => {
-                let r = room::set_room_name(self, server, access_token, roomid, name);
+            Ok(BKCommand::SetRoomName(server, access_token, room_id, name)) => {
+                let r = room::set_room_name(self, server, access_token, room_id, name);
                 bkerror2!(r, tx, BKResponse::SetRoomName);
             }
-            Ok(BKCommand::SetRoomTopic(server, access_token, roomid, topic)) => {
-                let r = room::set_room_topic(self, server, access_token, roomid, topic);
+            Ok(BKCommand::SetRoomTopic(server, access_token, room_id, topic)) => {
+                let r = room::set_room_topic(self, server, access_token, room_id, topic);
                 bkerror2!(r, tx, BKResponse::SetRoomTopic);
             }
-            Ok(BKCommand::SetRoomAvatar(server, access_token, roomid, fname)) => {
-                let r = room::set_room_avatar(self, server, access_token, roomid, fname);
+            Ok(BKCommand::SetRoomAvatar(server, access_token, room_id, fname)) => {
+                let r = room::set_room_avatar(self, server, access_token, room_id, fname);
                 bkerror2!(r, tx, BKResponse::SetRoomAvatar);
             }
             Ok(BKCommand::AttachFile(server, access_token, msg)) => {
                 let r = room::attach_file(self, server, access_token, msg);
                 bkerror2!(r, tx, BKResponse::AttachedFile);
             }
-            Ok(BKCommand::NewRoom(server, access_token, name, privacy, internalid)) => {
+            Ok(BKCommand::NewRoom(server, access_token, name, privacy, internal_id)) => {
                 let r = room::new_room(
                     self,
                     server,
                     access_token,
                     name,
                     privacy,
-                    internalid.clone(),
+                    internal_id.clone(),
                 );
                 if let Err(e) = r {
-                    tx.send(BKResponse::NewRoom(Err(e), internalid))
+                    tx.send(BKResponse::NewRoom(Err(e), internal_id))
                         .expect_log("Connection closed");
                 }
             }
-            Ok(BKCommand::DirectChat(server, access_token, uid, user, internalid)) => {
+            Ok(BKCommand::DirectChat(server, access_token, uid, user, internal_id)) => {
                 let r =
-                    room::direct_chat(self, server, access_token, uid, user, internalid.clone());
+                    room::direct_chat(self, server, access_token, uid, user, internal_id.clone());
                 if let Err(e) = r {
-                    tx.send(BKResponse::NewRoom(Err(e), internalid))
+                    tx.send(BKResponse::NewRoom(Err(e), internal_id))
                         .expect_log("Connection closed");
                 }
             }
-            Ok(BKCommand::AddToFav(server, access_token, uid, roomid, tofav)) => {
-                let r = room::add_to_fav(self, server, access_token, uid, roomid, tofav);
-                bkerror2!(r, tx, BKResponse::AddedToFav);
+            Ok(BKCommand::AddToFav(server, access_token, uid, room_id, tofav)) => {
+                thread::spawn(move || {
+                    let query = room::add_to_fav(server, access_token, uid, room_id, tofav);
+                    tx.send(BKResponse::AddedToFav(query))
+                        .expect_log("Connection closed");
+                });
             }
-            Ok(BKCommand::AcceptInv(server, access_token, roomid)) => {
-                let r = room::join_room(self, server, access_token, roomid);
-                bkerror2!(r, tx, BKResponse::JoinRoom);
+            Ok(BKCommand::AcceptInv(server, access_token, room_id)) => {
+                room::join_room(self, server, access_token, room_id)
             }
-            Ok(BKCommand::RejectInv(server, access_token, roomid)) => {
-                let r = room::leave_room(self, server, access_token, roomid);
-                bkerror2!(r, tx, BKResponse::LeaveRoom);
+            Ok(BKCommand::RejectInv(server, access_token, room_id)) => {
+                thread::spawn(move || {
+                    let query = room::leave_room(server, access_token, room_id);
+                    tx.send(BKResponse::LeaveRoom(query))
+                        .expect_log("Connection closed");
+                });
             }
-            Ok(BKCommand::Invite(server, access_token, room, userid)) => {
-                let r = room::invite(self, server, access_token, room, userid);
-                bkerror!(r, tx, BKResponse::InviteError);
+            Ok(BKCommand::Invite(server, access_token, room_id, userid)) => {
+                thread::spawn(move || {
+                    let query = room::invite(server, access_token, room_id, userid);
+                    if let Err(err) = query {
+                        tx.send(BKResponse::InviteError(err))
+                            .expect_log("Connection closed");
+                    }
+                });
             }
-            Ok(BKCommand::ChangeLanguage(access_token, server, uid, room, lang)) => {
-                let r = room::set_language(self, access_token, server, uid, room, lang);
+            Ok(BKCommand::ChangeLanguage(access_token, server, uid, room_id, lang)) => {
+                let r = room::set_language(self, access_token, server, uid, room_id, lang);
                 bkerror2!(r, tx, BKResponse::ChangeLanguage);
             }
 
@@ -337,7 +357,7 @@ impl Backend {
             Ok(BKCommand::GetMediaListAsync(
                 server,
                 access_token,
-                roomid,
+                room_id,
                 first_media_id,
                 prev_batch,
                 ctx,
@@ -345,7 +365,7 @@ impl Backend {
                 self,
                 server,
                 access_token,
-                roomid,
+                room_id,
                 first_media_id,
                 prev_batch,
                 ctx,
@@ -391,6 +411,10 @@ impl Backend {
             }
 
             // Internal commands
+            Ok(BKCommand::SendBKResponse(response)) => {
+                tx.send(response).expect_log("Connection closed");
+            }
+
             Ok(BKCommand::ShutDown) => {
                 tx.send(BKResponse::ShutDown)
                     .expect_log("Connection closed");
diff --git a/fractal-matrix-api/src/backend/room.rs b/fractal-matrix-api/src/backend/room.rs
index dea519a6..d031afe3 100644
--- a/fractal-matrix-api/src/backend/room.rs
+++ b/fractal-matrix-api/src/backend/room.rs
@@ -3,12 +3,12 @@ use serde_json::json;
 
 use ruma_identifiers::RoomId;
 use ruma_identifiers::RoomIdOrAliasId;
-use std::convert::TryFrom;
 use std::fs;
 use std::sync::mpsc::Sender;
 use url::Url;
 
 use std::collections::HashMap;
+use std::convert::TryFrom;
 use std::sync::{Arc, Mutex};
 use std::time::Duration;
 
@@ -68,11 +68,17 @@ pub fn set_room(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    id: String,
+    room_id: RoomId,
 ) -> Result<(), Error> {
     /* FIXME: remove clone and pass id by reference */
-    get_room_avatar(bk, base.clone(), access_token.clone(), id.clone())?;
-    get_room_detail(bk, base, access_token, id, String::from("m.room.topic"))?;
+    get_room_avatar(bk, base.clone(), access_token.clone(), room_id.clone())?;
+    get_room_detail(
+        bk,
+        base,
+        access_token,
+        room_id,
+        String::from("m.room.topic"),
+    )?;
 
     Ok(())
 }
@@ -81,13 +87,13 @@ pub fn get_room_detail(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     keys: String,
 ) -> Result<(), Error> {
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/state/{}", roomid, keys),
+        &format!("rooms/{}/state/{}", room_id, keys),
         vec![],
     )?;
 
@@ -98,7 +104,7 @@ pub fn get_room_detail(
             let k = keys.split('.').last().unwrap();
 
             let value = r[&k].as_str().map(Into::into).unwrap_or_default();
-            tx.send(BKResponse::RoomDetail(Ok((roomid, keys, value))))
+            tx.send(BKResponse::RoomDetail(Ok((room_id, keys, value))))
                 .expect_log("Connection closed");
         },
         |err| {
@@ -114,12 +120,12 @@ pub fn get_room_avatar(
     bk: &Backend,
     baseu: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
 ) -> Result<(), Error> {
     let url = bk.url(
         baseu.clone(),
         &access_token,
-        &format!("rooms/{}/state/m.room.avatar", roomid),
+        &format!("rooms/{}/state/m.room.avatar", room_id),
         vec![],
     )?;
     let tx = bk.tx.clone();
@@ -127,7 +133,7 @@ pub fn get_room_avatar(
         url,
         |r: JsonValue| {
             let avatar = r["url"].as_str().and_then(|s| Url::parse(s).ok());
-            let dest = cache_dir_path(None, &roomid).ok();
+            let dest = cache_dir_path(None, &room_id.to_string()).ok();
             if let Some(ref avatar) = avatar {
                 let _ = dw_media(
                     &baseu,
@@ -136,14 +142,14 @@ pub fn get_room_avatar(
                     dest.as_ref().map(String::as_str),
                 );
             }
-            tx.send(BKResponse::RoomAvatar(Ok((roomid, avatar))))
+            tx.send(BKResponse::RoomAvatar(Ok((room_id, avatar))))
                 .expect_log("Connection closed");
         },
         |err: Error| match err {
             Error::MatrixError(ref js)
                 if js["errcode"].as_str().unwrap_or_default() == "M_NOT_FOUND" =>
             {
-                tx.send(BKResponse::RoomAvatar(Ok((roomid, None))))
+                tx.send(BKResponse::RoomAvatar(Ok((room_id, None))))
                     .expect_log("Connection closed");
             }
             _ => {
@@ -157,37 +163,26 @@ pub fn get_room_avatar(
 }
 
 pub fn get_room_members(
-    bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
-) -> Result<(), Error> {
-    let tx = bk.tx.clone();
-
-    let room_id = RoomId::try_from(roomid.as_str())?;
+    room_id: RoomId,
+) -> Result<(RoomId, Vec<Member>), Error> {
     let params = JoinedMembersParameters { access_token };
 
-    thread::spawn(move || {
-        let query = get_joined_members(base, &room_id, &params)
-            .map_err(Into::into)
-            .and_then(|request| {
-                HTTP_CLIENT
-                    .get_client()?
-                    .execute(request)?
-                    .json::<JoinedMembersResponse>()
-                    .map_err(Into::into)
-            })
-            .map(|response| {
-                let ms = response.joined.into_iter().map(Member::from).collect();
-
-                (room_id.to_string(), ms)
-            });
-
-        tx.send(BKResponse::RoomMembers(query))
-            .expect_log("Connection closed");
-    });
+    get_joined_members(base, &room_id, &params)
+        .map_err(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)?
+                .json::<JoinedMembersResponse>()
+                .map_err(Into::into)
+        })
+        .map(|response| {
+            let ms = response.joined.into_iter().map(Member::from).collect();
 
-    Ok(())
+            (room_id, ms)
+        })
 }
 
 /* Load older messages starting by prev_batch
@@ -197,7 +192,7 @@ pub fn get_room_messages(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     from: String,
 ) -> Result<(), Error> {
     let params = vec![
@@ -216,7 +211,7 @@ pub fn get_room_messages(
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/messages", roomid),
+        &format!("rooms/{}/messages", room_id),
         params,
     )?;
     let tx = bk.tx.clone();
@@ -225,9 +220,9 @@ pub fn get_room_messages(
         |r: JsonValue| {
             let array = r["chunk"].as_array();
             let evs = array.unwrap().iter().rev();
-            let list = Message::from_json_events_iter(&roomid, evs);
+            let list = Message::from_json_events_iter(&room_id, evs);
             let prev_batch = r["end"].as_str().map(String::from);
-            tx.send(BKResponse::RoomMessagesTo(Ok((list, roomid, prev_batch))))
+            tx.send(BKResponse::RoomMessagesTo(Ok((list, room_id, prev_batch))))
                 .expect_log("Connection closed");
         },
         |err| {
@@ -243,7 +238,7 @@ pub fn get_room_messages_from_msg(
     bk: &Backend,
     baseu: Url,
     tk: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     msg: Message,
 ) {
     // first of all, we calculate the from param using the context api, then we call the
@@ -251,9 +246,9 @@ pub fn get_room_messages_from_msg(
     let itx = bk.internal_tx.clone();
 
     thread::spawn(move || {
-        if let Ok(from) = get_prev_batch_from(&baseu, &tk, &roomid, &msg.id) {
+        if let Ok(from) = get_prev_batch_from(&baseu, &tk, &room_id, &msg.id) {
             if let Some(t) = itx {
-                t.send(BKCommand::GetRoomMessages(baseu, tk, roomid, from))
+                t.send(BKCommand::GetRoomMessages(baseu, tk, room_id, from))
                     .expect_log("Connection closed");
             }
         }
@@ -264,13 +259,13 @@ fn parse_context(
     tx: Sender<BKResponse>,
     tk: AccessToken,
     baseu: Url,
-    roomid: String,
+    room_id: RoomId,
     eid: &str,
     limit: i32,
 ) -> Result<(), Error> {
     let url = client_url(
         &baseu,
-        &format!("rooms/{}/context/{}", roomid, eid),
+        &format!("rooms/{}/context/{}", room_id, eid),
         &[
             ("limit", format!("{}", limit)),
             ("access_token", tk.to_string()),
@@ -293,20 +288,20 @@ fn parse_context(
                     continue;
                 }
 
-                let m = Message::parse_room_message(&roomid, msg);
+                let m = Message::parse_room_message(&room_id, msg);
                 ms.push(m);
             }
 
             if ms.is_empty() && id.is_some() {
                 // there's no messages so we'll try with a bigger context
                 if let Err(err) =
-                    parse_context(tx.clone(), tk, baseu, roomid, &id.unwrap(), limit * 2)
+                    parse_context(tx.clone(), tk, baseu, room_id, &id.unwrap(), limit * 2)
                 {
                     tx.send(BKResponse::RoomMessagesTo(Err(err)))
                         .expect_log("Connection closed");
                 }
             } else {
-                tx.send(BKResponse::RoomMessagesTo(Ok((ms, roomid, None))))
+                tx.send(BKResponse::RoomMessagesTo(Ok((ms, room_id, None))))
                     .expect_log("Connection closed");
             }
         },
@@ -326,9 +321,9 @@ pub fn get_message_context(
     msg: Message,
 ) -> Result<(), Error> {
     let tx = bk.tx.clone();
-    let roomid = msg.room.clone();
+    let room_id: RoomId = msg.room.clone();
 
-    parse_context(tx, tk, baseu, roomid, &msg.id, globals::PAGE_LIMIT)?;
+    parse_context(tx, tk, baseu, room_id, &msg.id, globals::PAGE_LIMIT)?;
 
     Ok(())
 }
@@ -339,12 +334,12 @@ pub fn send_msg(
     access_token: AccessToken,
     msg: Message,
 ) -> Result<(), Error> {
-    let roomid = msg.room.clone();
+    let room_id: RoomId = msg.room.clone();
 
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/send/m.room.message/{}", roomid, msg.id),
+        &format!("rooms/{}/send/m.room.message/{}", room_id, msg.id),
         vec![],
     )?;
 
@@ -371,8 +366,7 @@ pub fn send_msg(
     }
 
     let tx = bk.tx.clone();
-    query!(
-        "put",
+    put!(
         url,
         &attrs,
         move |js: JsonValue| {
@@ -390,38 +384,23 @@ pub fn send_msg(
 }
 
 pub fn send_typing(
-    bk: &Backend,
     base: Url,
     access_token: AccessToken,
     userid: String,
-    roomid: String,
+    room_id: RoomId,
 ) -> Result<(), Error> {
-    let tx = bk.tx.clone();
-
-    let room_id = RoomId::try_from(roomid.as_str())?;
     let params = TypingNotificationParameters { access_token };
     let body = TypingNotificationBody::Typing(Duration::from_secs(4));
 
-    thread::spawn(move || {
-        let query = send_typing_notification(base, &room_id, &userid, &params, &body)
-            .map_err(Into::into)
-            .and_then(|request| {
-                HTTP_CLIENT
-                    .get_client()?
-                    .execute(request)
-                    .map_err(Into::into)
-            });
-
-        match query {
-            Err(err) => {
-                tx.send(BKResponse::SendTypingError(err))
-                    .expect_log("Connection closed");
-            }
-            _ => (),
-        }
-    });
-
-    Ok(())
+    send_typing_notification(base, &room_id, &userid, &params, &body)
+        .map_err(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)
+                .map_err(Into::into)
+        })
+        .and(Ok(()))
 }
 
 pub fn redact_msg(
@@ -430,13 +409,13 @@ pub fn redact_msg(
     access_token: AccessToken,
     msg: &Message,
 ) -> Result<(), Error> {
-    let roomid = msg.room.clone();
+    let room_id: RoomId = msg.room.clone();
     let txnid = msg.id.clone();
 
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/redact/{}/{}", roomid, msg.id, txnid),
+        &format!("rooms/{}/redact/{}/{}", room_id, msg.id, txnid),
         vec![],
     )?;
 
@@ -446,8 +425,7 @@ pub fn redact_msg(
 
     let msgid = msg.id.clone();
     let tx = bk.tx.clone();
-    query!(
-        "put",
+    put!(
         url,
         &attrs,
         move |js: JsonValue| {
@@ -466,16 +444,12 @@ pub fn redact_msg(
     Ok(())
 }
 
-pub fn join_room(
-    bk: &Backend,
-    base: Url,
-    access_token: AccessToken,
-    roomid: String,
-) -> Result<(), Error> {
+pub fn join_room(bk: &Backend, base: Url, access_token: AccessToken, room_id: RoomId) {
     let tx = bk.tx.clone();
     let data = bk.data.clone();
 
-    let room_id_or_alias_id = RoomIdOrAliasId::try_from(roomid.as_str())?;
+    let room_id_or_alias_id = RoomIdOrAliasId::RoomId(room_id.clone());
+
     let params = JoinRoomParameters {
         access_token,
         server_name: Default::default(),
@@ -490,61 +464,46 @@ pub fn join_room(
                     .execute(request)
                     .map_err(Into::into)
             })
-            .map(|_| {
-                data.lock().unwrap().join_to_room = room_id_or_alias_id.to_string();
-            });
+            .and(Ok(()));
+
+        if let Ok(_) = query {
+            data.lock().unwrap().join_to_room = Some(room_id);
+        }
 
         tx.send(BKResponse::JoinRoom(query))
             .expect_log("Connection closed");
     });
-
-    Ok(())
 }
 
-pub fn leave_room(
-    bk: &Backend,
-    base: Url,
-    access_token: AccessToken,
-    roomid: String,
-) -> Result<(), Error> {
-    let tx = bk.tx.clone();
-
-    let room_id = RoomId::try_from(roomid.as_str())?;
+pub fn leave_room(base: Url, access_token: AccessToken, room_id: RoomId) -> Result<(), Error> {
     let params = LeaveRoomParameters { access_token };
 
-    thread::spawn(move || {
-        let query = leave_room_req(base, &room_id, &params)
-            .map_err(Into::into)
-            .and_then(|request| {
-                HTTP_CLIENT
-                    .get_client()?
-                    .execute(request)
-                    .map_err(Into::into)
-            })
-            .and(Ok(()));
-
-        tx.send(BKResponse::LeaveRoom(query))
-            .expect_log("Connection closed");
-    });
-
-    Ok(())
+    leave_room_req(base, &room_id, &params)
+        .map_err(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)
+                .map_err(Into::into)
+        })
+        .and(Ok(()))
 }
 
 pub fn mark_as_read(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     eventid: String,
 ) -> Result<(), Error> {
     let url = bk.url(
         base.clone(),
         &access_token,
-        &format!("rooms/{}/receipt/m.read/{}", roomid, eventid),
+        &format!("rooms/{}/receipt/m.read/{}", room_id, eventid),
         vec![],
     )?;
 
-    let r = roomid.clone();
+    let r = room_id.clone();
     let e = eventid.clone();
     let tx = bk.tx.clone();
     post!(
@@ -566,7 +525,7 @@ pub fn mark_as_read(
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/read_markers", roomid),
+        &format!("rooms/{}/read_markers", room_id),
         vec![],
     )?;
     let attrs = json!({
@@ -582,13 +541,13 @@ pub fn set_room_name(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     name: String,
 ) -> Result<(), Error> {
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/state/m.room.name", roomid),
+        &format!("rooms/{}/state/m.room.name", room_id),
         vec![],
     )?;
 
@@ -597,8 +556,7 @@ pub fn set_room_name(
     });
 
     let tx = bk.tx.clone();
-    query!(
-        "put",
+    put!(
         url,
         &attrs,
         |_| {
@@ -618,13 +576,13 @@ pub fn set_room_topic(
     bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     topic: String,
 ) -> Result<(), Error> {
     let url = bk.url(
         base,
         &access_token,
-        &format!("rooms/{}/state/m.room.topic", roomid),
+        &format!("rooms/{}/state/m.room.topic", room_id),
         vec![],
     )?;
 
@@ -633,8 +591,7 @@ pub fn set_room_topic(
     });
 
     let tx = bk.tx.clone();
-    query!(
-        "put",
+    put!(
         url,
         &attrs,
         |_| {
@@ -654,13 +611,13 @@ pub fn set_room_avatar(
     bk: &Backend,
     baseu: Url,
     tk: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     avatar: String,
 ) -> Result<(), Error> {
     let roomurl = bk.url(
         baseu.clone(),
         &tk,
-        &format!("rooms/{}/state/m.room.avatar", roomid),
+        &format!("rooms/{}/state/m.room.avatar", room_id),
         vec![],
     )?;
 
@@ -777,7 +734,7 @@ pub fn new_room(
     access_token: AccessToken,
     name: String,
     privacy: RoomType,
-    internal_id: String,
+    internal_id: RoomId,
 ) -> Result<(), Error> {
     let url = bk.url(base, &access_token, "createRoom", vec![])?;
     let attrs = json!({
@@ -800,10 +757,14 @@ pub fn new_room(
         url,
         &attrs,
         move |r: JsonValue| {
-            let id = String::from(r["room_id"].as_str().unwrap_or_default());
-            let mut r = Room::new(id, RoomMembership::Joined(RoomTag::None));
-            r.name = Some(name);
-            tx.send(BKResponse::NewRoom(Ok(r), internal_id))
+            let room_res = RoomId::try_from(r["room_id"].as_str().unwrap_or_default())
+                .map_err(Into::into)
+                .map(|room_id| Room {
+                    name: Some(name),
+                    ..Room::new(room_id, RoomMembership::Joined(RoomTag::None))
+                });
+
+            tx.send(BKResponse::NewRoom(room_res, internal_id))
                 .expect_log("Connection closed");
         },
         |err| {
@@ -814,7 +775,7 @@ pub fn new_room(
     Ok(())
 }
 
-pub fn update_direct_chats(url: Url, data: Arc<Mutex<BackendData>>, user: String, room: String) {
+pub fn update_direct_chats(url: Url, data: Arc<Mutex<BackendData>>, user: String, room_id: RoomId) {
     get!(
         url.clone(),
         |r: JsonValue| {
@@ -833,10 +794,10 @@ pub fn update_direct_chats(url: Url, data: Arc<Mutex<BackendData>>, user: String
 
             if directs.contains_key(&user) {
                 if let Some(v) = directs.get_mut(&user) {
-                    v.push(room)
+                    v.push(room_id.to_string())
                 };
             } else {
-                directs.insert(user, vec![room]);
+                directs.insert(user, vec![room_id.to_string()]);
             }
             data.lock().unwrap().m_direct = directs.clone();
 
@@ -855,7 +816,7 @@ pub fn direct_chat(
     access_token: AccessToken,
     userid: String,
     user: Member,
-    internal_id: String,
+    internal_id: RoomId,
 ) -> Result<(), Error> {
     let url = bk.url(base.clone(), &access_token, "createRoom", vec![])?;
     let attrs = json!({
@@ -885,14 +846,24 @@ pub fn direct_chat(
         url,
         &attrs,
         move |r: JsonValue| {
-            let id = String::from(r["room_id"].as_str().unwrap_or_default());
-            let mut r = Room::new(id.clone(), RoomMembership::Joined(RoomTag::None));
-            r.name = user.alias.clone();
-            r.direct = true;
-            tx.send(BKResponse::NewRoom(Ok(r), internal_id))
-                .expect_log("Connection closed");
+            match RoomId::try_from(r["room_id"].as_str().unwrap_or_default()) {
+                Ok(room_id) => {
+                    let r = Room {
+                        name: user.alias.clone(),
+                        direct: true,
+                        ..Room::new(room_id.clone(), RoomMembership::Joined(RoomTag::None))
+                    };
+
+                    tx.send(BKResponse::NewRoom(Ok(r), internal_id))
+                        .expect_log("Connection closed");
 
-            update_direct_chats(direct_url, data, user.uid.clone(), id);
+                    update_direct_chats(direct_url, data, user.uid.clone(), room_id);
+                }
+                Err(err) => {
+                    tx.send(BKResponse::NewRoom(Err(err.into()), internal_id))
+                        .expect_log("Connection closed");
+                }
+            }
         },
         |err| {
             tx.send(BKResponse::NewRoom(Err(err), internal_id))
@@ -904,76 +875,50 @@ pub fn direct_chat(
 }
 
 pub fn add_to_fav(
-    bk: &Backend,
     base: Url,
     access_token: AccessToken,
     userid: String,
-    roomid: String,
+    room_id: RoomId,
     tofav: bool,
-) -> Result<(), Error> {
-    let tx = bk.tx.clone();
-
-    let room_id = RoomId::try_from(roomid.as_str())?;
-
-    thread::spawn(move || {
-        let request_res = if tofav {
-            let params = CreateTagParameters { access_token };
-            let body = CreateTagBody { order: Some(0.5) };
-            create_tag(base, &userid, &room_id, "m.favourite", &params, &body)
-        } else {
-            let params = DeleteTagParameters { access_token };
-            delete_tag(base, &userid, &room_id, "m.favourite", &params)
-        };
-
-        let query = request_res
-            .map_err(Into::into)
-            .and_then(|request| {
-                HTTP_CLIENT
-                    .get_client()?
-                    .execute(request)
-                    .map_err(Into::into)
-            })
-            .and(Ok((room_id.to_string(), tofav)));
-
-        tx.send(BKResponse::AddedToFav(query))
-            .expect_log("Connection closed");
-    });
+) -> Result<(RoomId, bool), Error> {
+    let request_res = if tofav {
+        let params = CreateTagParameters { access_token };
+        let body = CreateTagBody { order: Some(0.5) };
+        create_tag(base, &userid, &room_id, "m.favourite", &params, &body)
+    } else {
+        let params = DeleteTagParameters { access_token };
+        delete_tag(base, &userid, &room_id, "m.favourite", &params)
+    };
 
-    Ok(())
+    request_res
+        .map_err(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)
+                .map_err(Into::into)
+        })
+        .and(Ok((room_id, tofav)))
 }
 
 pub fn invite(
-    bk: &Backend,
     base: Url,
     access_token: AccessToken,
-    roomid: String,
+    room_id: RoomId,
     user_id: String,
 ) -> Result<(), Error> {
-    let tx = bk.tx.clone();
-
-    let room_id = RoomId::try_from(roomid.as_str())?;
     let params = InviteUserParameters { access_token };
     let body = InviteUserBody { user_id };
 
-    thread::spawn(move || {
-        let query = invite_user(base, &room_id, &params, &body)
-            .map_err(Into::into)
-            .and_then(|request| {
-                HTTP_CLIENT
-                    .get_client()?
-                    .execute(request)
-                    .map_err(Into::into)
-            });
-
-        match query {
-            Err(err) => {
-                let _ = tx.send(BKResponse::InviteError(err));
-            }
-            _ => (),
-        }
-    });
-
-    Ok(())
+    invite_user(base, &room_id, &params, &body)
+        .map_err(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)
+                .map_err(Into::into)
+        })
+        .and(Ok(()))
 }
 
 pub fn set_language(
@@ -981,7 +926,7 @@ pub fn set_language(
     access_token: AccessToken,
     server: Url,
     userid: String,
-    roomid: String,
+    room_id: RoomId,
     input_language: String,
 ) -> Result<(), Error> {
     let url = bk.url(
@@ -989,8 +934,7 @@ pub fn set_language(
         &access_token,
         &format!(
             "user/{}/rooms/{}/account_data/org.gnome.fractal.language",
-            userid,
-            roomid.clone()
+            userid, room_id,
         ),
         vec![],
     )?;
diff --git a/fractal-matrix-api/src/backend/sync.rs b/fractal-matrix-api/src/backend/sync.rs
index 30fec620..99625106 100644
--- a/fractal-matrix-api/src/backend/sync.rs
+++ b/fractal-matrix-api/src/backend/sync.rs
@@ -237,12 +237,12 @@ pub fn sync(
                     data.lock().unwrap().m_direct = parse_m_direct(&response.account_data.events);
 
                     let rooms = Room::from_sync_response(&response, &userid, &base);
-                    let jtr = data.lock().unwrap().join_to_room.clone();
-                    let def = if !jtr.is_empty() {
-                        rooms.iter().find(|x| x.id == jtr).cloned()
-                    } else {
-                        None
-                    };
+                    let def = data
+                        .lock()
+                        .unwrap()
+                        .join_to_room
+                        .as_ref()
+                        .and_then(|jtr| rooms.iter().find(|x| x.id == *jtr).cloned());
                     tx.send(BKResponse::Rooms(rooms, def))
                         .expect_log("Connection closed");
                 }
diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs
index 9c700553..a66cfe45 100644
--- a/fractal-matrix-api/src/backend/types.rs
+++ b/fractal-matrix-api/src/backend/types.rs
@@ -1,3 +1,4 @@
+use ruma_identifiers::RoomId;
 use std::collections::HashMap;
 use std::sync::mpsc::Sender;
 use std::sync::{Arc, Condvar, Mutex};
@@ -37,17 +38,17 @@ pub enum BKCommand {
     GetAvatar(Url, String),
     SetUserAvatar(Url, AccessToken, String, String),
     Sync(Url, AccessToken, String, Option<String>, bool),
-    GetRoomMembers(Url, AccessToken, String),
-    GetRoomMessages(Url, AccessToken, String, String),
-    GetRoomMessagesFromMsg(Url, AccessToken, String, Message),
+    GetRoomMembers(Url, AccessToken, RoomId),
+    GetRoomMessages(Url, AccessToken, RoomId, String),
+    GetRoomMessagesFromMsg(Url, AccessToken, RoomId, Message),
     GetMessageContext(Url, AccessToken, Message),
-    GetRoomAvatar(Url, AccessToken, String),
+    GetRoomAvatar(Url, AccessToken, RoomId),
     GetThumbAsync(Url, String, Sender<Result<String, Error>>),
     GetMediaAsync(Url, String, Sender<Result<String, Error>>),
     GetMediaListAsync(
         Url,
         AccessToken,
-        String,
+        RoomId,
         Option<String>,
         Option<String>,
         Sender<(Vec<Message>, String)>,
@@ -59,26 +60,27 @@ pub enum BKCommand {
     GetUserNameAsync(Url, String, Sender<String>),
     SendMsg(Url, AccessToken, Message),
     SendMsgRedaction(Url, AccessToken, Message),
-    SendTyping(Url, AccessToken, String, String),
-    SetRoom(Url, AccessToken, String),
+    SendTyping(Url, AccessToken, String, RoomId),
+    SetRoom(Url, AccessToken, RoomId),
     ShutDown,
     DirectoryProtocols(Url, AccessToken),
     DirectorySearch(Url, AccessToken, String, String, String, bool),
-    JoinRoom(Url, AccessToken, String),
-    MarkAsRead(Url, AccessToken, String, String),
-    LeaveRoom(Url, AccessToken, String),
-    SetRoomName(Url, AccessToken, String, String),
-    SetRoomTopic(Url, AccessToken, String, String),
-    SetRoomAvatar(Url, AccessToken, String, String),
+    JoinRoom(Url, AccessToken, RoomId),
+    MarkAsRead(Url, AccessToken, RoomId, String),
+    LeaveRoom(Url, AccessToken, RoomId),
+    SetRoomName(Url, AccessToken, RoomId, String),
+    SetRoomTopic(Url, AccessToken, RoomId, String),
+    SetRoomAvatar(Url, AccessToken, RoomId, String),
     AttachFile(Url, AccessToken, Message),
-    NewRoom(Url, AccessToken, String, RoomType, String),
-    DirectChat(Url, AccessToken, String, Member, String),
-    AddToFav(Url, AccessToken, String, String, bool),
-    AcceptInv(Url, AccessToken, String),
-    RejectInv(Url, AccessToken, String),
+    NewRoom(Url, AccessToken, String, RoomType, RoomId),
+    DirectChat(Url, AccessToken, String, Member, RoomId),
+    AddToFav(Url, AccessToken, String, RoomId, bool),
+    AcceptInv(Url, AccessToken, RoomId),
+    RejectInv(Url, AccessToken, RoomId),
     UserSearch(Url, AccessToken, String),
-    Invite(Url, AccessToken, String, String),
-    ChangeLanguage(AccessToken, Url, String, String, String),
+    Invite(Url, AccessToken, RoomId, String),
+    ChangeLanguage(AccessToken, Url, String, RoomId, String),
+    SendBKResponse(BKResponse),
 }
 
 #[derive(Debug)]
@@ -101,33 +103,33 @@ pub enum BKResponse {
     Sync(Result<String, Error>),
     Rooms(Vec<Room>, Option<Room>),
     UpdateRooms(Vec<Room>),
-    RoomDetail(Result<(String, String, String), Error>),
-    RoomAvatar(Result<(String, Option<Url>), Error>),
-    NewRoomAvatar(String),
+    RoomDetail(Result<(RoomId, String, String), Error>),
+    RoomAvatar(Result<(RoomId, Option<Url>), Error>),
+    NewRoomAvatar(RoomId),
     RoomMemberEvent(Event),
     RoomMessages(Vec<Message>),
     RoomMessagesInit(Vec<Message>),
-    RoomMessagesTo(Result<(Vec<Message>, String, Option<String>), Error>),
-    RoomMembers(Result<(String, Vec<Member>), Error>),
+    RoomMessagesTo(Result<(Vec<Message>, RoomId, Option<String>), Error>),
+    RoomMembers(Result<(RoomId, Vec<Member>), Error>),
     SentMsg(Result<(String, String), Error>),
     SentMsgRedaction(Result<(String, String), Error>),
     DirectoryProtocols(Result<Vec<ProtocolInstance>, Error>),
     DirectorySearch(Result<Vec<Room>, Error>),
     JoinRoom(Result<(), Error>),
     LeaveRoom(Result<(), Error>),
-    MarkedAsRead(Result<(String, String), Error>),
+    MarkedAsRead(Result<(RoomId, String), Error>),
     SetRoomName(Result<(), Error>),
     SetRoomTopic(Result<(), Error>),
     SetRoomAvatar(Result<(), Error>),
-    RemoveMessage(Result<(String, String), Error>),
-    RoomName(String, String),
-    RoomTopic(String, String),
+    RemoveMessage(Result<(RoomId, String), Error>),
+    RoomName(RoomId, String),
+    RoomTopic(RoomId, String),
     Media(Result<String, Error>),
     MediaUrl(Url),
     AttachedFile(Result<Message, Error>),
-    NewRoom(Result<Room, Error>, String),
-    AddedToFav(Result<(String, bool), Error>),
-    RoomNotifications(String, i32, i32),
+    NewRoom(Result<Room, Error>, RoomId),
+    AddedToFav(Result<(RoomId, bool), Error>),
+    RoomNotifications(RoomId, i32, i32),
     UserSearch(Result<Vec<Member>, Error>),
 
     //errors
@@ -148,7 +150,7 @@ pub enum RoomType {
 
 pub struct BackendData {
     pub rooms_since: String,
-    pub join_to_room: String,
+    pub join_to_room: Option<RoomId>,
     pub m_direct: HashMap<String, Vec<String>>,
 }
 
diff --git a/fractal-matrix-api/src/model/event.rs b/fractal-matrix-api/src/model/event.rs
index cdd20e64..83bdbf58 100644
--- a/fractal-matrix-api/src/model/event.rs
+++ b/fractal-matrix-api/src/model/event.rs
@@ -1,10 +1,11 @@
+use ruma_identifiers::RoomId;
 use serde_json::Value as JsonValue;
 
 #[derive(Debug, Clone)]
 pub struct Event {
     pub sender: String,
     pub stype: String,
-    pub room: String,
+    pub room: RoomId,
     pub id: String,
     pub redacts: String,
     pub content: JsonValue,
diff --git a/fractal-matrix-api/src/model/message.rs b/fractal-matrix-api/src/model/message.rs
index 85a79904..884d5dbe 100644
--- a/fractal-matrix-api/src/model/message.rs
+++ b/fractal-matrix-api/src/model/message.rs
@@ -1,6 +1,7 @@
 use chrono::prelude::*;
 use chrono::DateTime;
 use chrono::TimeZone;
+use ruma_identifiers::RoomId;
 use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
 use std::cmp::Ordering;
@@ -13,7 +14,7 @@ pub struct Message {
     pub mtype: String,
     pub body: String,
     pub date: DateTime<Local>,
-    pub room: String,
+    pub room: RoomId,
     pub thumb: Option<String>,
     pub url: Option<String>,
     pub id: String,
@@ -47,7 +48,7 @@ impl PartialOrd for Message {
 }
 
 impl Message {
-    pub fn new(room: String, sender: String, body: String, mtype: String) -> Self {
+    pub fn new(room: RoomId, sender: String, body: String, mtype: String) -> Self {
         let date = Local::now();
         Message {
             id: get_txn_id(&room, &body, &date.to_string()),
@@ -95,7 +96,7 @@ impl Message {
     ///
     /// * `roomid` - The message room id
     /// * `msg` - The message event as Json
-    pub fn parse_room_message(roomid: &str, msg: &JsonValue) -> Message {
+    pub fn parse_room_message(room_id: &RoomId, msg: &JsonValue) -> Message {
         let sender = msg["sender"].as_str().unwrap_or_default();
 
         let timestamp = msg["origin_server_ts"].as_i64().unwrap_or_default() / 1000;
@@ -109,7 +110,7 @@ impl Message {
         let mut message = Message {
             sender: sender.to_string(),
             date: server_timestamp,
-            room: String::from(roomid),
+            room: room_id.clone(),
             id: id.to_string(),
             mtype: type_.to_string(),
             body: String::new(),
@@ -126,78 +127,77 @@ impl Message {
 
         let c = &msg["content"];
         match type_ {
-            "m.room.message" => Message::parse_m_room_message(&mut message, c),
-            "m.sticker" => Message::parse_m_sticker(&mut message, c),
+            "m.room.message" => message.parse_m_room_message(c),
+            "m.sticker" => message.parse_m_sticker(c),
             _ => {}
         };
 
         message
     }
 
-    fn parse_m_room_message(msg: &mut Message, c: &JsonValue) {
-        let mtype = c["msgtype"].as_str().unwrap_or_default();
-        let body = c["body"].as_str().unwrap_or_default();
+    fn parse_m_room_message(&mut self, c: &JsonValue) {
+        let mtype = c["msgtype"].as_str().map(String::from).unwrap_or_default();
+        let body = c["body"].as_str().map(String::from).unwrap_or_default();
         let formatted_body = c["formatted_body"].as_str().map(String::from);
         let format = c["format"].as_str().map(String::from);
 
-        match mtype {
+        match mtype.as_str() {
             "m.image" | "m.file" | "m.video" | "m.audio" => {
-                let url = String::from(c["url"].as_str().unwrap_or_default());
-                let mut t = String::from(c["info"]["thumbnail_url"].as_str().unwrap_or_default());
+                let url = c["url"].as_str().map(String::from).unwrap_or_default();
+                let mut t = c["info"]["thumbnail_url"]
+                    .as_str()
+                    .map(String::from)
+                    .unwrap_or_default();
                 if t.is_empty() && !url.is_empty() {
                     t = url.clone();
                 }
 
-                msg.url = Some(url);
-                msg.thumb = Some(t);
+                self.url = Some(url);
+                self.thumb = Some(t);
             }
             "m.text" => {
                 // Only m.text messages can be replies for backward compatability
                 // https://matrix.org/docs/spec/client_server/r0.4.0.html#rich-replies
-                msg.in_reply_to = c["m.relates_to"]["m.in_reply_to"]["event_id"]
+                self.in_reply_to = c["m.relates_to"]["m.in_reply_to"]["event_id"]
                     .as_str()
-                    .map(String::from)
+                    .map(String::from);
             }
             _ => {}
         };
 
-        msg.mtype = mtype.to_string();
-        msg.body = body.to_string();
-        msg.formatted_body = formatted_body;
-        msg.format = format;
+        self.mtype = mtype;
+        self.body = body;
+        self.formatted_body = formatted_body;
+        self.format = format;
     }
 
-    fn parse_m_sticker(msg: &mut Message, c: &JsonValue) {
-        let body = c["body"].as_str().unwrap_or_default();
-
-        let url = String::from(c["url"].as_str().unwrap_or_default());
-        let mut t = String::from(c["info"]["thumbnail_url"].as_str().unwrap_or_default());
+    fn parse_m_sticker(&mut self, c: &JsonValue) {
+        let url = c["url"].as_str().map(String::from).unwrap_or_default();
+        let mut t = c["info"]["thumbnail_url"]
+            .as_str()
+            .map(String::from)
+            .unwrap_or_default();
         if t.is_empty() && !url.is_empty() {
             t = url.clone();
         }
 
-        msg.body = body.to_string();
-        msg.url = Some(url);
-        msg.thumb = Some(t);
+        self.body = c["body"].as_str().map(String::from).unwrap_or_default();
+        self.url = Some(url);
+        self.thumb = Some(t);
     }
 
     /// Create a vec of Message from a json event list
     ///
     /// * `roomid` - The messages room id
     /// * `events` - An iterator to the json events
-    pub fn from_json_events_iter<'a, I>(roomid: &str, events: I) -> Vec<Message>
+    pub fn from_json_events_iter<'a, I>(room_id: &RoomId, events: I) -> Vec<Message>
     where
         I: Iterator<Item = &'a JsonValue>,
     {
-        let mut ms = vec![];
-
-        let evs = events.filter(Message::supported_event);
-        for msg in evs {
-            let m = Message::parse_room_message(roomid.clone(), msg);
-            ms.push(m);
-        }
-
-        ms
+        events
+            .filter(Message::supported_event)
+            .map(|msg| Message::parse_room_message(&room_id, msg))
+            .collect()
     }
 
     pub fn set_receipt(&mut self, receipt: HashMap<String, i64>) {
@@ -209,7 +209,7 @@ impl Message {
 /// message body and the date.
 
 /// 
https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
-pub fn get_txn_id(room: &str, body: &str, date: &str) -> String {
+pub fn get_txn_id(room: &RoomId, body: &str, date: &str) -> String {
     let msg = format!("{}{}{}", room, body, date);
     let digest = md5::compute(msg.as_bytes());
     format!("{:x}", digest)
diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs
index 7186d5a6..e250c4e3 100644
--- a/fractal-matrix-api/src/model/room.rs
+++ b/fractal-matrix-api/src/model/room.rs
@@ -8,6 +8,7 @@ use crate::r0::sync::sync_events::Response as SyncResponse;
 use crate::util::get_user_avatar;
 use crate::util::parse_m_direct;
 use log::{debug, info};
+use ruma_identifiers::RoomId;
 use serde::{Deserialize, Serialize};
 use std::collections::{HashMap, HashSet};
 use url::Url;
@@ -76,9 +77,9 @@ pub enum RoomTag {
     Custom(String),
 }
 
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Room {
-    pub id: String,
+    pub id: RoomId,
     pub avatar: Option<String>, // TODO: Use Option<Url>
     pub name: Option<String>,
     pub topic: Option<String>,
@@ -103,13 +104,27 @@ pub struct Room {
 }
 
 impl Room {
-    pub fn new(id: String, membership: RoomMembership) -> Room {
+    pub fn new(id: RoomId, membership: RoomMembership) -> Room {
         Room {
             id,
             membership,
             guest_can_join: true,
             world_readable: true,
-            ..Default::default()
+            avatar: Default::default(),
+            name: Default::default(),
+            topic: Default::default(),
+            alias: Default::default(),
+            n_members: Default::default(),
+            members: Default::default(),
+            notifications: Default::default(),
+            highlight: Default::default(),
+            messages: Default::default(),
+            direct: Default::default(),
+            prev_batch: Default::default(),
+            typing_users: Default::default(),
+            language: Default::default(),
+            admins: Default::default(),
+            power_levels: Default::default(),
         }
     }
 
@@ -143,7 +158,7 @@ impl Room {
                 avatar: evc(stevents, "m.room.avatar", "url"),
                 alias: evc(stevents, "m.room.canonical_alias", "alias"),
                 topic: evc(stevents, "m.room.topic", "topic"),
-                direct: direct.contains(k),
+                direct: direct.contains(&k.to_string()),
                 notifications: room.unread_notifications.notification_count,
                 highlight: room.unread_notifications.highlight_count,
                 prev_batch: timeline.prev_batch.clone(),
@@ -228,7 +243,7 @@ impl Room {
                     avatar: evc(stevents, "m.room.avatar", "url"),
                     alias: evc(stevents, "m.room.canonical_alias", "alias"),
                     topic: evc(stevents, "m.room.topic", "topic"),
-                    direct: direct.contains(k),
+                    direct: direct.contains(&k.to_string()),
                     ..Self::new(k.clone(), RoomMembership::Invited(inv_sender))
                 })
             } else {
@@ -294,7 +309,7 @@ impl From<PublicRoomsChunk> for Room {
             n_members: input.num_joined_members,
             world_readable: input.world_readable,
             guest_can_join: input.guest_can_join,
-            ..Self::new(input.room_id.to_string(), RoomMembership::None)
+            ..Self::new(input.room_id, RoomMembership::None)
         }
     }
 }
@@ -305,7 +320,7 @@ impl PartialEq for Room {
     }
 }
 
-pub type RoomList = HashMap<String, Room>;
+pub type RoomList = HashMap<RoomId, Room>;
 
 fn evc(events: &Vec<JsonValue>, t: &str, field: &str) -> Option<String> {
     events
diff --git a/fractal-matrix-api/src/r0/sync/sync_events.rs b/fractal-matrix-api/src/r0/sync/sync_events.rs
index d3c2e6ae..5aaf9bc2 100644
--- a/fractal-matrix-api/src/r0/sync/sync_events.rs
+++ b/fractal-matrix-api/src/r0/sync/sync_events.rs
@@ -3,6 +3,7 @@ use crate::r0::AccessToken;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
+use ruma_identifiers::RoomId;
 use serde::ser::SerializeMap;
 use serde::{Deserialize, Serialize, Serializer};
 use serde_json::Value as JsonValue;
@@ -111,11 +112,11 @@ pub struct Response {
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct Rooms {
     #[serde(default)]
-    pub leave: HashMap<String, LeftRoom>,
+    pub leave: HashMap<RoomId, LeftRoom>,
     #[serde(default)]
-    pub join: HashMap<String, JoinedRoom>,
+    pub join: HashMap<RoomId, JoinedRoom>,
     #[serde(default)]
-    pub invite: HashMap<String, InvitedRoom>,
+    pub invite: HashMap<RoomId, InvitedRoom>,
 }
 
 #[derive(Clone, Debug, Deserialize)]
diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs
index 47db4fdb..202bda53 100644
--- a/fractal-matrix-api/src/util.rs
+++ b/fractal-matrix-api/src/util.rs
@@ -5,6 +5,7 @@ use serde_json::json;
 use serde_json::Value as JsonValue;
 
 use directories::ProjectDirs;
+use ruma_identifiers::RoomId;
 use std::collections::HashMap;
 use std::io::Read;
 use std::path::Path;
@@ -193,12 +194,12 @@ pub fn parse_m_direct(events: &Vec<JsonValue>) -> HashMap<String, Vec<String>> {
 pub fn get_prev_batch_from(
     baseu: &Url,
     tk: &AccessToken,
-    roomid: &str,
+    room_id: &RoomId,
     evid: &str,
 ) -> Result<String, Error> {
     let params = &[("access_token", tk.to_string()), ("limit", 0.to_string())];
 
-    let path = format!("rooms/{}/context/{}", roomid, evid);
+    let path = format!("rooms/{}/context/{}", room_id, evid);
     let url = client_url(baseu, &path, params)?;
 
     let r = json_q("get", url, &json!(null))?;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]