diff options
Diffstat (limited to '')
36 files changed, 920 insertions, 384 deletions
diff --git a/.helix/languages.toml b/.helix/languages.toml index 7662fff..02fab3a 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -1,9 +1,9 @@ [language-server.rust-analyzer] command = "rust-analyzer" -environment = { "DATABASE_URL" = "sqlite://filamento/filamento.db" } +# environment = { "DATABASE_URL" = "sqlite://filamento/filamento.db" } # checkOnSave.overrideCommand = "cargo check --message-format=json -p luz", # check.overrideCommand="cargo check --message-format=json -p luz", # check.workspace = false, # cargo.target = "wasm32-unknown-unknown", -config = { cargo.target = "wasm32-unknown-unknown", cargo.features = ["jid/rusqlite", "stanza/rfc_6121", "stanza/xep_0203", "stanza/rfc_7395", "stanza/xep_0030", "stanza/xep_0060", "stanza/xep_0172", "stanza/xep_0390", "stanza/xep_0128", "stanza/xep_0115", "stanza/xep_0084", "uuid/v4", "tokio/full", "rsasl/provider_base64", "rsasl/plain", "rsasl/config_builder", "rsasl/scram-sha-1", "stanza/xep_0156"] } +# config = { cargo.target = "wasm32-unknown-unknown", cargo.features = ["jid/rusqlite", "stanza/rfc_6121", "stanza/xep_0203", "stanza/rfc_7395", "stanza/xep_0030", "stanza/xep_0060", "stanza/xep_0172", "stanza/xep_0390", "stanza/xep_0128", "stanza/xep_0115", "stanza/xep_0084", "uuid/v4", "tokio/full", "rsasl/provider_base64", "rsasl/plain", "rsasl/config_builder", "rsasl/scram-sha-1", "stanza/xep_0156"] } # "sqlx/sqlite", "sqlx/runtime-tokio", "sqlx/uuid", "sqlx/chrono", "jid/sqlx", diff --git a/filamento/Cargo.toml b/filamento/Cargo.toml index 7d7af0c..b89c577 100644 --- a/filamento/Cargo.toml +++ b/filamento/Cargo.toml @@ -52,6 +52,7 @@ tokio = { workspace = true, features = ["sync", "time", "rt", "fs", "io-std"] } [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { workspace = true, features = ["sync", "time", "rt"] } +js-sys.workspace = true web-sys = { workspace = true, features = [ "FileSystemDirectoryHandle", "FileSystemWritableFileStream", diff --git a/filamento/src/caps.rs b/filamento/src/caps.rs index 43f1cf4..e0587ff 100644 --- a/filamento/src/caps.rs +++ b/filamento/src/caps.rs @@ -377,7 +377,7 @@ pub fn node_to_hash(node: String) -> Result<Hash, HashNodeConversionError> { #[cfg(test)] mod tests { - use peanuts::{Writer, element::IntoElement, loggable::Loggable}; + use peanuts::Writer; use stanza::{ xep_0004::{Field, FieldType, Value, X, XType}, xep_0030::info::{Feature, Identity}, diff --git a/filamento/src/chat.rs b/filamento/src/chat.rs index 5f58866..c02654f 100644 --- a/filamento/src/chat.rs +++ b/filamento/src/chat.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Write}; use chrono::{DateTime, Utc}; -use jid::JID; +use jid::{BareJID, JID}; use rusqlite::{ ToSql, types::{FromSql, ToSqlOutput, Value}, @@ -15,7 +15,7 @@ pub struct Message { pub id: Uuid, // does not contain full user information // bare jid (for now) - pub from: JID, + pub from: BareJID, pub delivery: Option<Delivery>, pub timestamp: DateTime<Utc>, // TODO: originally_from @@ -97,7 +97,7 @@ pub struct Body { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct Chat { - pub correspondent: JID, + pub correspondent: BareJID, pub have_chatted: bool, // pub unread_messages: i32, // pub latest_message: Message, @@ -109,13 +109,13 @@ pub struct Chat { pub enum ChatUpdate {} impl Chat { - pub fn new(correspondent: JID, have_chatted: bool) -> Self { + pub fn new(correspondent: BareJID, have_chatted: bool) -> Self { Self { correspondent, have_chatted, } } - pub fn correspondent(&self) -> &JID { + pub fn correspondent(&self) -> &BareJID { &self.correspondent } } diff --git a/filamento/src/db.rs b/filamento/src/db.rs index bac1b7c..298d54a 100644 --- a/filamento/src/db.rs +++ b/filamento/src/db.rs @@ -2,7 +2,7 @@ use core::fmt::Display; use std::{collections::HashSet, ops::Deref, path::Path, sync::Arc}; use chrono::{DateTime, Utc}; -use jid::JID; +use jid::{BareJID, FullJID, JID}; use rusqlite::{Connection, OptionalExtension}; use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{mpsc, oneshot}; @@ -88,12 +88,21 @@ impl Db { spawn_blocking(move || { spawn_local(async move { debug!("installing opfs in spawn"); - rusqlite::ffi::install_opfs_sahpool( + match rusqlite::ffi::install_opfs_sahpool( Some(&rusqlite::ffi::OpfsSAHPoolCfg::default()), false, ) .await - .unwrap(); + { + Ok(_) => {} + Err(e) => { + use crate::error::OpfsSAHError; + + let error: OpfsSAHError = e.into(); + result_send.send(Err(error.into())); + return; + } + } debug!("opfs installed"); let file_name = format!("file:{}?vfs=opfs-sahpool", file_name.as_ref()); let result = DbActor::new(file_name, receiver); @@ -152,7 +161,7 @@ impl Db { } // TODO: this is not a 'read' user - pub(crate) async fn read_user(&self, user: JID) -> Result<User, Error> { + pub(crate) async fn read_user(&self, user: BareJID) -> Result<User, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadUser { user, result }; self.sender.send(command); @@ -161,7 +170,7 @@ impl Db { } /// returns whether or not the nickname was updated - pub(crate) async fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> { + pub(crate) async fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteUserNick { jid, result }; self.sender.send(command); @@ -170,7 +179,7 @@ impl Db { } /// returns whether or not the nickname was updated - pub(crate) async fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> { + pub(crate) async fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpsertUserNick { jid, nick, result }; self.sender.send(command); @@ -181,7 +190,7 @@ impl Db { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) async fn delete_user_avatar( &self, - jid: JID, + jid: BareJID, ) -> Result<(bool, Option<String>), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteUserAvatar { jid, result }; @@ -193,7 +202,7 @@ impl Db { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) async fn upsert_user_avatar( &self, - jid: JID, + jid: BareJID, avatar: String, ) -> Result<(bool, Option<String>), Error> { let (result, recv) = oneshot::channel(); @@ -228,7 +237,7 @@ impl Db { result } - pub(crate) async fn read_contact(&self, contact: JID) -> Result<Contact, Error> { + pub(crate) async fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadContact { contact, result }; self.sender.send(command); @@ -236,7 +245,10 @@ impl Db { result } - pub(crate) async fn read_contact_opt(&self, contact: JID) -> Result<Option<Contact>, Error> { + pub(crate) async fn read_contact_opt( + &self, + contact: BareJID, + ) -> Result<Option<Contact>, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadContactOpt { contact, result }; self.sender.send(command); @@ -261,7 +273,7 @@ impl Db { result } - pub(crate) async fn delete_contact(&self, contact: JID) -> Result<(), Error> { + pub(crate) async fn delete_contact(&self, contact: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteContact { contact, result }; self.sender.send(command); @@ -305,7 +317,7 @@ impl Db { // TODO: what happens if a correspondent changes from a user to a contact? maybe just have correspondent be a user, then have the client make the user show up as a contact in ui if they are in the loaded roster. - pub(crate) async fn read_chat(&self, chat: JID) -> Result<Chat, Error> { + pub(crate) async fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadChat { chat, result }; self.sender.send(command); @@ -313,7 +325,7 @@ impl Db { result } - pub(crate) async fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> { + pub(crate) async fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadChatAndUser { chat, result }; self.sender.send(command); @@ -321,7 +333,7 @@ impl Db { result } - pub(crate) async fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> { + pub(crate) async fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::MarkChatAsChatted { chat, result }; self.sender.send(command); @@ -332,7 +344,7 @@ impl Db { pub(crate) async fn update_chat_correspondent( &self, old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, ) -> Result<Chat, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpdateChatCorrespondent { @@ -347,7 +359,7 @@ impl Db { // pub(crate) async fn update_chat - pub(crate) async fn delete_chat(&self, chat: JID) -> Result<(), Error> { + pub(crate) async fn delete_chat(&self, chat: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteChat { chat, result }; self.sender.send(command); @@ -403,8 +415,8 @@ impl Db { pub(crate) async fn create_message( &self, message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::CreateMessage { @@ -418,7 +430,7 @@ impl Db { result } - pub(crate) async fn upsert_chat_and_user(&self, chat: JID) -> Result<bool, Error> { + pub(crate) async fn upsert_chat_and_user(&self, chat: BareJID) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpsertChatAndUser { chat, result }; self.sender.send(command); @@ -432,10 +444,8 @@ impl Db { &self, message: Message, // TODO: enforce two kinds of jid. bare and full - // must be bare jid - chat: JID, - // full jid - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { tracing::info!("MSGDEBUG create_message_with_user_resource exists"); let (result, recv) = oneshot::channel(); @@ -483,9 +493,9 @@ impl Db { DeleteCachedStatus => delete_cached_status() -> (); UpsertCachedStatus => upsert_cached_status(status: Online) -> (); ReadCachedStatus => read_cached_status() -> Online; - ReadMessageHistoryWithUsers => read_message_history_with_users(chat: JID) -> Vec<(Message, User)>; + ReadMessageHistoryWithUsers => read_message_history_with_users(chat: BareJID) -> Vec<(Message, User)>; // TODO: paging - ReadMessageHistory => read_message_history(chat: JID) -> Vec<Message>; + ReadMessageHistory => read_message_history(chat: BareJID) -> Vec<Message>; ReadMessage => read_message(message: Uuid) -> Message; DeleteMessage => delete_message(message: Uuid) -> () ); @@ -515,24 +525,24 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadUser { - user: JID, + user: BareJID, result: oneshot::Sender<Result<User, Error>>, }, DeleteUserNick { - jid: JID, + jid: BareJID, result: oneshot::Sender<Result<bool, Error>>, }, UpsertUserNick { - jid: JID, + jid: BareJID, nick: String, result: oneshot::Sender<Result<bool, Error>>, }, DeleteUserAvatar { - jid: JID, + jid: BareJID, result: oneshot::Sender<Result<(bool, Option<String>), Error>>, }, UpsertUserAvatar { - jid: JID, + jid: BareJID, avatar: String, result: oneshot::Sender<Result<(bool, Option<String>), Error>>, }, @@ -545,11 +555,11 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadContact { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<Contact, Error>>, }, ReadContactOpt { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<Option<Contact>, Error>>, }, UpdateContact { @@ -561,7 +571,7 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, DeleteContact { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<(), Error>>, }, ReplaceCachedRoster { @@ -579,24 +589,24 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadChat { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Chat, Error>>, }, ReadChatAndUser { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(Chat, User), Error>>, }, MarkChatAsChatted { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(), Error>>, }, UpdateChatCorrespondent { old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, result: oneshot::Sender<Result<Chat, Error>>, }, DeleteChat { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(), Error>>, }, ReadChats { @@ -621,18 +631,18 @@ pub enum DbCommand { // }, CreateMessage { message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, result: oneshot::Sender<Result<(), Error>>, }, UpsertChatAndUser { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<bool, Error>>, }, CreateMessageWithUserResource { message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, result: oneshot::Sender<Result<(), Error>>, }, UpdateMessageDelivery { @@ -649,11 +659,11 @@ pub enum DbCommand { result: oneshot::Sender<Result<Message, Error>>, }, ReadMessageHistory { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Vec<Message>, Error>>, }, ReadMessageHistoryWithUsers { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Vec<(Message, User)>, Error>>, }, ReadCachedStatus { @@ -823,7 +833,7 @@ impl DbActor { result.send(self.read_contact(contact)); } DbCommand::ReadContactOpt { contact, result } => { - result.send(self.read_contact_opt(&contact)); + result.send(self.read_contact_opt(contact)); } DbCommand::UpdateContact { contact, result } => { result.send(self.update_contact(contact)); @@ -951,7 +961,7 @@ impl DbActor { } // TODO: this is not a 'read' user - pub(crate) fn read_user(&self, user: JID) -> Result<User, Error> { + pub(crate) fn read_user(&self, user: BareJID) -> Result<User, Error> { let db = &self.db; let user_opt = db .query_row( @@ -980,7 +990,7 @@ impl DbActor { } /// returns whether or not the nickname was updated - pub(crate) fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> { + pub(crate) fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> { let rows_affected; { rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, None::<String>, None::<String>, None::<String>))?; @@ -993,7 +1003,7 @@ impl DbActor { } /// returns whether or not the nickname was updated - pub(crate) fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> { + pub(crate) fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> { let rows_affected; { rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, &nick, &nick, &nick))?; @@ -1006,7 +1016,7 @@ impl DbActor { } /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar - pub(crate) fn delete_user_avatar(&self, jid: JID) -> Result<(bool, Option<String>), Error> { + pub(crate) fn delete_user_avatar(&self, jid: BareJID) -> Result<(bool, Option<String>), Error> { let (old_avatar, rows_affected): (Option<String>, _); { let db = &self.db; @@ -1027,7 +1037,7 @@ impl DbActor { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) fn upsert_user_avatar( &self, - jid: JID, + jid: BareJID, avatar: String, ) -> Result<(bool, Option<String>), Error> { let (old_avatar, rows_affected): (Option<String>, _); @@ -1081,7 +1091,7 @@ impl DbActor { Ok(()) } - pub(crate) fn read_contact(&self, contact: JID) -> Result<Contact, Error> { + pub(crate) fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> { let db = &self.db; let mut contact_item = db.query_row( "select user_jid, name, subscription from roster where user_jid = ?1", @@ -1103,7 +1113,7 @@ impl DbActor { Ok(contact_item) } - pub(crate) fn read_contact_opt(&self, contact: &JID) -> Result<Option<Contact>, Error> { + pub(crate) fn read_contact_opt(&self, contact: BareJID) -> Result<Option<Contact>, Error> { let db = &self.db; let contact_item = db .query_row( @@ -1183,7 +1193,7 @@ impl DbActor { Ok(()) } - pub(crate) fn delete_contact(&self, contact: JID) -> Result<(), Error> { + pub(crate) fn delete_contact(&self, contact: BareJID) -> Result<(), Error> { self.db .execute("delete from roster where user_jid = ?1", [&contact])?; Ok(()) @@ -1261,19 +1271,21 @@ impl DbActor { // TODO: what happens if a correspondent changes from a user to a contact? maybe just have correspondent be a user, then have the client make the user show up as a contact in ui if they are in the loaded roster. - /// should be a bare jid /// TODO: this is NOT a read - pub(crate) fn read_chat(&self, chat: JID) -> Result<Chat, Error> { - let chat_opt = self.db.query_row( - "select correspondent, have_chatted from chats where correspondent = ?1", - [&chat], - |row| { - Ok(Chat { - correspondent: row.get(0)?, - have_chatted: row.get(1)?, - }) - }, - ).optional()?; + pub(crate) fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> { + let chat_opt = self + .db + .query_row( + "select correspondent, have_chatted from chats where correspondent = ?1", + [&chat], + |row| { + Ok(Chat { + correspondent: row.get(0)?, + have_chatted: row.get(1)?, + }) + }, + ) + .optional()?; match chat_opt { Some(chat) => return Ok(chat), None => { @@ -1287,7 +1299,7 @@ impl DbActor { } } - pub(crate) fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> { + pub(crate) fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> { let user = self.read_user(chat.clone())?; let chat_opt = self.db.query_row( "select correspondent, have_chatted, jid, nick, avatar from chats join users on correspondent = jid where correspondent = ?1", @@ -1319,7 +1331,7 @@ impl DbActor { } } - pub(crate) fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> { + pub(crate) fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> { self.db.execute( "update chats set have_chatted = true where correspondent = ?1", [chat], @@ -1330,7 +1342,7 @@ impl DbActor { pub(crate) fn update_chat_correspondent( &self, old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, ) -> Result<Chat, Error> { let new_jid = &new_correspondent; let old_jid = old_chat.correspondent(); @@ -1347,7 +1359,7 @@ impl DbActor { // pub(crate) fn update_chat - pub(crate) fn delete_chat(&self, chat: JID) -> Result<(), Error> { + pub(crate) fn delete_chat(&self, chat: BareJID) -> Result<(), Error> { self.db .execute("delete from chats where correspondent = ?1", [chat])?; Ok(()) @@ -1457,7 +1469,7 @@ impl DbActor { } #[tracing::instrument] - fn read_chat_id(&self, chat: JID) -> Result<Uuid, Error> { + fn read_chat_id(&self, chat: BareJID) -> Result<Uuid, Error> { let chat_id = self.db.query_row( "select id from chats where correspondent = ?1", [chat], @@ -1483,8 +1495,8 @@ impl DbActor { pub(crate) fn create_message( &self, message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let from_jid = from.as_bare(); let chat_id = self.read_chat_id(chat)?; @@ -1493,18 +1505,17 @@ impl DbActor { Ok(()) } - pub(crate) fn upsert_chat_and_user(&self, chat: &JID) -> Result<bool, Error> { - let bare_chat = chat.as_bare(); + pub(crate) fn upsert_chat_and_user(&self, chat: &BareJID) -> Result<bool, Error> { let db = &self.db; db.execute( "insert into users (jid) values (?1) on conflict do nothing", - [&bare_chat], + [&chat], )?; let id = Uuid::new_v4(); - db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &bare_chat, false))?; + db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &chat, false))?; let chat = db.query_row( "select correspondent, have_chatted from chats where correspondent = ?1", - [&bare_chat], + [&chat], |row| { Ok(Chat { correspondent: row.get(0)?, @@ -1520,21 +1531,15 @@ impl DbActor { pub(crate) fn create_message_with_user_resource( &self, message: Message, - // TODO: enforce two kinds of jid. bare and full - // must be bare jid - chat: JID, - // full jid - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let from_jid = from.as_bare(); - let chat = chat.as_bare(); tracing::debug!("creating resource"); - if let Some(resource) = &from.resourcepart { - self.db.execute( - "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing", - (&from_jid, resource), - )?; - } + self.db.execute( + "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing", + (&from_jid, &from.resourcepart), + )?; self.create_message(message, chat, from)?; Ok(()) } @@ -1588,7 +1593,7 @@ impl DbActor { } // TODO: paging - pub(crate) fn read_message_history(&self, chat: JID) -> Result<Vec<Message>, Error> { + pub(crate) fn read_message_history(&self, chat: BareJID) -> Result<Vec<Message>, Error> { let chat_id = self.read_chat_id(chat)?; let messages = self .db @@ -1611,7 +1616,7 @@ impl DbActor { pub(crate) fn read_message_history_with_users( &self, - chat: JID, + chat: BareJID, ) -> Result<Vec<(Message, User)>, Error> { let chat_id = self.read_chat_id(chat)?; let messages = self diff --git a/filamento/src/error.rs b/filamento/src/error.rs index f9f9199..fb7d778 100644 --- a/filamento/src/error.rs +++ b/filamento/src/error.rs @@ -3,6 +3,7 @@ use std::{num::TryFromIntError, string::FromUtf8Error, sync::Arc}; use base64::DecodeError; use image::ImageError; use jid::JID; +use jid::JIDError; use lampada::error::{ActorError, ReadError, WriteError}; use stanza::client::{Stanza, iq::Query}; use thiserror::Error; @@ -292,6 +293,9 @@ pub enum IqProcessError { #[derive(Debug, Error, Clone)] pub enum DatabaseOpenError { + #[cfg(target_arch = "wasm32")] + #[error("opfs: {0}")] + OpfsSAH(#[from] OpfsSAHError), #[error("error: {0}")] Error(Arc<rusqlite::Error>), // #[error("migration: {0}")] @@ -310,6 +314,117 @@ pub enum DatabaseOpenError { // } // } +#[cfg(target_arch = "wasm32")] +impl From<rusqlite::ffi::OpfsSAHError> for OpfsSAHError { + fn from(e: rusqlite::ffi::OpfsSAHError) -> Self { + use wasm_bindgen::UnwrapThrowExt; + match e { + rusqlite::ffi::OpfsSAHError::Vfs(_register_vfs_error) => Self::VfsRegistration, + rusqlite::ffi::OpfsSAHError::ImportDb(_import_db_error) => Self::ImportDb, + rusqlite::ffi::OpfsSAHError::NotSuported => Self::NotSupported, + rusqlite::ffi::OpfsSAHError::GetDirHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetDirHandle(message) + } + rusqlite::ffi::OpfsSAHError::GetFileHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetFileHandle(message) + } + rusqlite::ffi::OpfsSAHError::CreateSyncAccessHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::CreateSyncAccessHandle(message) + } + rusqlite::ffi::OpfsSAHError::IterHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::IterHandle(message) + } + rusqlite::ffi::OpfsSAHError::GetPath(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetPath(message) + } + rusqlite::ffi::OpfsSAHError::RemoveEntity(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::RemoveEntity(message) + } + rusqlite::ffi::OpfsSAHError::GetSize(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetSize(message) + } + rusqlite::ffi::OpfsSAHError::Read(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Read(message) + } + rusqlite::ffi::OpfsSAHError::Write(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Write(message) + } + rusqlite::ffi::OpfsSAHError::Flush(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Flush(message) + } + rusqlite::ffi::OpfsSAHError::Truncate(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Truncate(message) + } + rusqlite::ffi::OpfsSAHError::Reflect(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Reflect(message) + } + rusqlite::ffi::OpfsSAHError::Generic(s) => Self::Generic(s), + rusqlite::ffi::OpfsSAHError::Custom(s) => Self::Generic(s), + } + } +} + +#[cfg(target_arch = "wasm32")] +#[derive(Debug, Error, Clone)] +pub enum OpfsSAHError { + #[error("VFS registration")] + VfsRegistration, + #[error("import db error")] + ImportDb, + #[error("not supported")] + NotSupported, + #[error("get dir handle: {0}")] + GetDirHandle(String), + #[error("get file handle: {0}")] + GetFileHandle(String), + #[error("create sync access handle: {0}")] + CreateSyncAccessHandle(String), + #[error("iter handle: {0}")] + IterHandle(String), + #[error("get path: {0}")] + GetPath(String), + #[error("remove entity: {0}")] + RemoveEntity(String), + #[error("get size: {0}")] + GetSize(String), + #[error("read: {0}")] + Read(String), + #[error("write: {0}")] + Write(String), + #[error("flush: {0}")] + Flush(String), + #[error("truncate: {0}")] + Truncate(String), + #[error("reflect: {0}")] + Reflect(String), + #[error("generic: {0}")] + Generic(String), +} + impl From<std::io::Error> for DatabaseOpenError { fn from(e: std::io::Error) -> Self { Self::Io(Arc::new(e)) @@ -332,6 +447,8 @@ pub enum PresenceError { MissingFrom, #[error("stanza error: {0:?}")] StanzaError(Option<stanza::client::error::Error>), + #[error("received subscription request from a non-bare jid")] + InvalidSubscriptionRequest(#[from] JIDError), } #[derive(Debug, Error, Clone)] diff --git a/filamento/src/files/opfs.rs b/filamento/src/files/opfs.rs index fb32c6e..0bcce35 100644 --- a/filamento/src/files/opfs.rs +++ b/filamento/src/files/opfs.rs @@ -10,7 +10,7 @@ use web_sys::{ use crate::FileStore; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FilesOPFS { directory: String, } diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs index 068bfe8..d3033b7 100644 --- a/filamento/src/lib.rs +++ b/filamento/src/lib.rs @@ -16,7 +16,7 @@ use error::{ }; use files::FileStore; use futures::FutureExt; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{ Connected, CoreClient, CoreClientCommand, Logic, SupervisorSender, WriteMessage, error::{ActorError, CommandError, ConnectionError, ReadError, WriteError}, @@ -68,45 +68,55 @@ pub enum Command<Fs: FileStore> { oneshot::Sender<Result<Vec<((Chat, User), (Message, User))>, DatabaseError>>, ), /// get a specific chat by jid - GetChat(JID, oneshot::Sender<Result<Chat, DatabaseError>>), + GetChat(BareJID, oneshot::Sender<Result<Chat, DatabaseError>>), /// get a specific chat and user by jid - GetChatAndUser(JID, oneshot::Sender<Result<(Chat, User), DatabaseError>>), + GetChatAndUser( + BareJID, + oneshot::Sender<Result<(Chat, User), DatabaseError>>, + ), /// get message history for chat (does appropriate mam things) GetMessage(Uuid, oneshot::Sender<Result<Message, DatabaseError>>), // TODO: paging and filtering - GetMessages(JID, oneshot::Sender<Result<Vec<Message>, DatabaseError>>), + GetMessages( + BareJID, + oneshot::Sender<Result<Vec<Message>, DatabaseError>>, + ), /// get message history for chat (does appropriate mam things) // TODO: paging and filtering GetMessagesWithUsers( - JID, + BareJID, oneshot::Sender<Result<Vec<(Message, User)>, DatabaseError>>, ), /// delete a chat from your chat history, along with all the corresponding messages - DeleteChat(JID, oneshot::Sender<Result<(), DatabaseError>>), + DeleteChat(BareJID, oneshot::Sender<Result<(), DatabaseError>>), /// delete a message from your chat history DeleteMessage(Uuid, oneshot::Sender<Result<(), DatabaseError>>), /// get a user from your users database - GetUser(JID, oneshot::Sender<Result<User, DatabaseError>>), + GetUser(BareJID, oneshot::Sender<Result<User, DatabaseError>>), /// add a contact to your roster, with a status of none, no subscriptions. - AddContact(JID, oneshot::Sender<Result<(), RosterError>>), + AddContact(BareJID, oneshot::Sender<Result<(), RosterError>>), /// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster. - BuddyRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + BuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// send a subscription request, without pre-approval. if not already added to roster server adds to roster. - SubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + SubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster. - AcceptBuddyRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + AcceptBuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster. - AcceptSubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + AcceptSubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// unsubscribe to a contact, but don't remove their subscription. - UnsubscribeFromContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnsubscribeFromContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// stop a contact from being subscribed, but stay subscribed to the contact. - UnsubscribeContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnsubscribeContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// remove subscriptions to and from contact, but keep in roster. - UnfriendContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnfriendContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster. - DeleteContact(JID, oneshot::Sender<Result<(), RosterError>>), + DeleteContact(BareJID, oneshot::Sender<Result<(), RosterError>>), /// update contact. contact details will be overwritten with the contents of the contactupdate struct. - UpdateContact(JID, ContactUpdate, oneshot::Sender<Result<(), RosterError>>), + UpdateContact( + BareJID, + ContactUpdate, + oneshot::Sender<Result<(), RosterError>>, + ), /// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence. SetStatus(Online, oneshot::Sender<Result<(), StatusError>>), /// send presence stanza @@ -120,7 +130,7 @@ pub enum Command<Fs: FileStore> { // TODO: should probably make it so people can add non-contact auto presence sharing in the client (most likely through setting an internal setting) /// send a message to a jid (any kind of jid that can receive a message, e.g. a user or a /// chatroom). if disconnected, will be cached so when client connects, message will be sent. - SendMessage(JID, Body), + SendMessage(BareJID, Body), // TODO: resend failed messages // ResendMessage(Uuid), /// disco info query @@ -146,7 +156,7 @@ pub enum Command<Fs: FileStore> { sender: oneshot::Sender<Result<(), PEPError>>, }, GetPEPItem { - jid: Option<JID>, + jid: Option<BareJID>, node: String, id: String, sender: oneshot::Sender<Result<pep::Item, PEPError>>, @@ -171,7 +181,7 @@ pub enum UpdateMessage { Offline(Offline), /// (only update app roster state, don't replace) RosterUpdate(Contact, User), - RosterDelete(JID), + RosterDelete(BareJID), /// presences should be stored with users in the ui, not contacts, as presences can be received from anyone Presence { from: JID, @@ -180,22 +190,23 @@ pub enum UpdateMessage { // TODO: receipts // MessageDispatched(Uuid), Message { - to: JID, + // TODO: rename to chat? + to: BareJID, from: User, message: Message, }, MessageDelivery { id: Uuid, - chat: JID, + chat: BareJID, delivery: Delivery, }, - SubscriptionRequest(jid::JID), + SubscriptionRequest(BareJID), NickChanged { - jid: JID, + jid: BareJID, nick: Option<String>, }, AvatarChanged { - jid: JID, + jid: BareJID, id: Option<String>, }, } @@ -259,7 +270,7 @@ impl<Fs: FileStore + Clone + Send + Sync + 'static> Client<Fs> { timeout: Duration::from_secs(20), }; - let logic = ClientLogic::new(client.clone(), jid.as_bare(), db, update_send, file_store); + let logic = ClientLogic::new(client.clone(), jid.to_bare(), db, update_send, file_store); let actor: CoreClient<ClientLogic<Fs>> = CoreClient::new(jid, password, command_receiver, None, sup_recv, logic); @@ -373,7 +384,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(chats) } - pub async fn get_chat(&self, jid: JID) -> Result<Chat, CommandError<DatabaseError>> { + pub async fn get_chat(&self, jid: BareJID) -> Result<Chat, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetChat(jid, send))) .await @@ -385,12 +396,17 @@ impl<Fs: FileStore> Client<Fs> { Ok(chat) } - pub async fn get_chat_and_user(&self, jid: JID) -> Result<(Chat, User), CommandError<DatabaseError>> { + pub async fn get_chat_and_user( + &self, + jid: BareJID, + ) -> Result<(Chat, User), CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); - self.send(CoreClientCommand::Command(Command::GetChatAndUser(jid, send))) - .await - .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?; - let result= timeout(self.timeout, recv) + self.send(CoreClientCommand::Command(Command::GetChatAndUser( + jid, send, + ))) + .await + .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?; + let result = timeout(self.timeout, recv) .await .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))? .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??; @@ -411,7 +427,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_messages( &self, - jid: JID, + jid: BareJID, ) -> Result<Vec<Message>, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetMessages(jid, send))) @@ -426,7 +442,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_messages_with_users( &self, - jid: JID, + jid: BareJID, ) -> Result<Vec<(Message, User)>, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetMessagesWithUsers( @@ -441,7 +457,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(messages) } - pub async fn delete_chat(&self, jid: JID) -> Result<(), CommandError<DatabaseError>> { + pub async fn delete_chat(&self, jid: BareJID) -> Result<(), CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::DeleteChat(jid, send))) .await @@ -465,7 +481,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn get_user(&self, jid: JID) -> Result<User, CommandError<DatabaseError>> { + pub async fn get_user(&self, jid: BareJID) -> Result<User, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetUser(jid, send))) .await @@ -477,7 +493,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn add_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> { + pub async fn add_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::AddContact(jid, send))) .await @@ -489,7 +505,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn buddy_request(&self, jid: BareJID) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::BuddyRequest(jid, send))) .await @@ -501,7 +517,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn subscription_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn subscription_request( + &self, + jid: BareJID, + ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::SubscriptionRequest( jid, send, @@ -515,7 +534,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn accept_buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn accept_buddy_request( + &self, + jid: BareJID, + ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::AcceptBuddyRequest( jid, send, @@ -531,7 +553,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn accept_subscription_request( &self, - jid: JID, + jid: BareJID, ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command( @@ -546,7 +568,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unsubscribe_from_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unsubscribe_from_contact( + &self, + jid: BareJID, + ) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnsubscribeFromContact( jid, send, @@ -560,7 +585,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unsubscribe_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unsubscribe_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnsubscribeContact( jid, send, @@ -574,7 +599,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unfriend_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unfriend_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnfriendContact( jid, send, @@ -588,7 +613,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn delete_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> { + pub async fn delete_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::DeleteContact( jid, send, @@ -604,7 +629,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn update_contact( &self, - jid: JID, + jid: BareJID, update: ContactUpdate, ) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); @@ -632,7 +657,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn send_message(&self, jid: JID, body: Body) -> Result<(), ActorError> { + pub async fn send_message(&self, jid: BareJID, body: Body) -> Result<(), ActorError> { self.send(CoreClientCommand::Command(Command::SendMessage(jid, body))) .await?; Ok(()) @@ -711,7 +736,8 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_pep_item( &self, - jid: Option<JID>, + // i think this is correct?, should not be able to send pep requests to a full jid. + jid: Option<BareJID>, node: String, id: String, ) -> Result<pep::Item, CommandError<PEPError>> { @@ -811,7 +837,7 @@ mod tests { info!("sending message"); client .send_message( - JID::from_str("cel@blos.sm").unwrap(), + BareJID::from_str("cel@blos.sm").unwrap(), chat::Body { body: "hallo!!!".to_string(), }, @@ -821,7 +847,7 @@ mod tests { info!("sent message"); client .send_message( - JID::from_str("cel@blos.sm").unwrap(), + BareJID::from_str("cel@blos.sm").unwrap(), chat::Body { body: "hallo 2".to_string(), }, diff --git a/filamento/src/logic/connect.rs b/filamento/src/logic/connect.rs index 9d61ca4..6e392f1 100644 --- a/filamento/src/logic/connect.rs +++ b/filamento/src/logic/connect.rs @@ -11,7 +11,7 @@ use crate::{ use super::ClientLogic; -pub async fn handle_connect<Fs: FileStore + Clone + Send + Sync>( +pub async fn handle_connect<Fs: FileStore + Clone + Send + Sync + 'static>( logic: ClientLogic<Fs>, connection: Connected, ) { diff --git a/filamento/src/logic/local_only.rs b/filamento/src/logic/local_only.rs index f5705f4..7f3a2e6 100644 --- a/filamento/src/logic/local_only.rs +++ b/filamento/src/logic/local_only.rs @@ -1,4 +1,4 @@ -use jid::JID; +use jid::{BareJID, JID}; use uuid::Uuid; use crate::{ @@ -39,14 +39,14 @@ pub async fn handle_get_chats_ordered_with_latest_messages_and_users<Fs: FileSto pub async fn handle_get_chat<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Chat, DatabaseError> { Ok(logic.db().read_chat(jid).await?) } pub async fn handle_get_chat_and_user<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<(Chat, User), DatabaseError> { Ok(logic.db().read_chat_and_user(jid).await?) } @@ -60,21 +60,21 @@ pub async fn handle_get_message<Fs: FileStore + Clone>( pub async fn handle_get_messages<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Vec<Message>, DatabaseError> { Ok(logic.db().read_message_history(jid).await?) } pub async fn handle_get_messages_with_users<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Vec<(Message, User)>, DatabaseError> { Ok(logic.db().read_message_history_with_users(jid).await?) } pub async fn handle_delete_chat<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<(), DatabaseError> { Ok(logic.db().delete_chat(jid).await?) } @@ -88,7 +88,7 @@ pub async fn handle_delete_messaage<Fs: FileStore + Clone>( pub async fn handle_get_user<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<User, DatabaseError> { Ok(logic.db().read_user(jid).await?) } diff --git a/filamento/src/logic/mod.rs b/filamento/src/logic/mod.rs index 5e05dac..ddf0343 100644 --- a/filamento/src/logic/mod.rs +++ b/filamento/src/logic/mod.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{Connected, Logic, error::ReadError}; use stanza::client::Stanza; use tokio::sync::{Mutex, mpsc, oneshot}; @@ -25,7 +25,7 @@ mod process_stanza; #[derive(Clone)] pub struct ClientLogic<Fs: FileStore> { client: Client<Fs>, - bare_jid: JID, + jid: BareJID, db: Db, pending: Pending, update_sender: mpsc::Sender<UpdateMessage>, @@ -80,7 +80,7 @@ impl Pending { impl<Fs: FileStore> ClientLogic<Fs> { pub fn new( client: Client<Fs>, - bare_jid: JID, + jid: BareJID, db: Db, update_sender: mpsc::Sender<UpdateMessage>, file_store: Fs, @@ -90,7 +90,7 @@ impl<Fs: FileStore> ClientLogic<Fs> { pending: Pending::new(), update_sender, client, - bare_jid, + jid, file_store, } } @@ -127,7 +127,7 @@ impl<Fs: FileStore> ClientLogic<Fs> { } } -impl<Fs: FileStore + Clone + Send + Sync> Logic for ClientLogic<Fs> { +impl<Fs: FileStore + Clone + Send + Sync + 'static> Logic for ClientLogic<Fs> { type Cmd = Command<Fs>; // pub async fn handle_stream_error(self, error) {} diff --git a/filamento/src/logic/offline.rs b/filamento/src/logic/offline.rs index 606b04f..aa84f3d 100644 --- a/filamento/src/logic/offline.rs +++ b/filamento/src/logic/offline.rs @@ -1,6 +1,7 @@ use std::process::id; use chrono::Utc; +use jid::FullJID; use lampada::error::WriteError; use tracing::error; use uuid::Uuid; @@ -19,9 +20,13 @@ use crate::{ }; use super::{ + ClientLogic, local_only::{ - handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user, handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages, handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message, handle_get_messages, handle_get_messages_with_users, handle_get_user - }, ClientLogic + handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user, + handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages, + handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message, + handle_get_messages, handle_get_messages_with_users, handle_get_user, + }, }; pub async fn handle_offline<Fs: FileStore + Clone>(logic: ClientLogic<Fs>, command: Command<Fs>) { @@ -153,7 +158,7 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( let message = Message { id, - from: logic.bare_jid.clone(), + from: logic.jid.clone(), // TODO: failure reason delivery: Some(Delivery::Failed), timestamp, @@ -163,11 +168,15 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( // TODO: mark these as potentially failed upon client launch if let Err(e) = logic .db() + // TODO: can create message without a resource.... .create_message_with_user_resource( message.clone(), jid.clone(), // TODO: when message is queued and sent, the from must also be updated with the correct resource - logic.bare_jid.clone(), + FullJID { + bare_jid: logic.jid.clone(), + resourcepart: "unsent".to_string(), + }, ) .await { @@ -177,12 +186,12 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( .await; } - let from = match logic.db().read_user(logic.bare_jid.clone()).await { + let from = match logic.db().read_user(logic.jid.clone()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: logic.bare_jid.clone(), + jid: logic.jid.clone(), nick: None, avatar: None, } @@ -192,7 +201,7 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( logic .update_sender() .send(crate::UpdateMessage::Message { - to: jid.as_bare(), + to: jid, message, from, }) diff --git a/filamento/src/logic/online.rs b/filamento/src/logic/online.rs index 9814ff2..b36f9a9 100644 --- a/filamento/src/logic/online.rs +++ b/filamento/src/logic/online.rs @@ -3,7 +3,7 @@ use std::{io::Cursor, time::Duration}; use base64::{prelude::BASE64_STANDARD, Engine}; use chrono::Utc; use image::ImageReader; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{Connected, WriteMessage, error::WriteError}; use sha1::{Digest, Sha1}; use stanza::{ @@ -11,7 +11,9 @@ use stanza::{ iq::{self, Iq, IqType, Query}, Stanza }, xep_0030::{info, items}, xep_0060::{self, owner, pubsub::{self, Pubsub}}, xep_0084, xep_0172::{self, Nick}, xep_0203::Delay }; -use tokio::sync::oneshot; +use tokio::{sync::oneshot, task::spawn_blocking}; +#[cfg(target_arch = "wasm32")] +use tokio_with_wasm::alias as tokio; use tracing::{debug, error, info}; use uuid::Uuid; @@ -27,7 +29,7 @@ use super::{ }, ClientLogic }; -pub async fn handle_online<Fs: FileStore + Clone>(logic: ClientLogic<Fs>, command: Command<Fs>, connection: Connected) { +pub async fn handle_online<Fs: FileStore + Clone + 'static>(logic: ClientLogic<Fs>, command: Command<Fs>, connection: Connected) { let result = handle_online_result(&logic, command, connection).await; match result { Ok(_) => {} @@ -41,7 +43,7 @@ pub async fn handle_get_roster<Fs: FileStore + Clone>( ) -> Result<Vec<Contact>, RosterError> { let iq_id = Uuid::new_v4().to_string(); let stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.to_string(), to: None, r#type: IqType::Get, @@ -101,7 +103,7 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>( ) -> Result<Vec<(Contact, User)>, RosterError> { let iq_id = Uuid::new_v4().to_string(); let stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.to_string(), to: None, r#type: IqType::Get, @@ -162,11 +164,11 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>( pub async fn handle_add_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -220,9 +222,10 @@ pub async fn handle_add_contact<Fs: FileStore + Clone>( pub async fn handle_buddy_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let jid: JID = jid.into(); + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), @@ -243,13 +246,13 @@ pub async fn handle_buddy_request<Fs: FileStore + Clone>( pub async fn handle_subscription_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { // TODO: i should probably have builders - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Subscribe), nick, ..Default::default() @@ -261,15 +264,16 @@ pub async fn handle_subscription_request<Fs: FileStore + Clone>( pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { + let jid: JID = jid.into(); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), r#type: Some(stanza::client::presence::PresenceType::Subscribed), ..Default::default() }); connection.write_handle().write(presence).await?; - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid), @@ -284,14 +288,11 @@ pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>( pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; - let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), - r#type: Some(stanza::client::presence::PresenceType::Subscribe), - nick, + to: Some(jid.into()), + r#type: Some(stanza::client::presence::PresenceType::Subscribed), ..Default::default() }); connection.write_handle().write(presence).await?; @@ -300,10 +301,10 @@ pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>( pub async fn handle_unsubscribe_from_contact( connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), WriteError> { let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribe), ..Default::default() }); @@ -311,9 +312,9 @@ pub async fn handle_unsubscribe_from_contact( Ok(()) } -pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Result<(), WriteError> { +pub async fn handle_unsubscribe_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> { let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribed), ..Default::default() }); @@ -321,7 +322,8 @@ pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Resu Ok(()) } -pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result<(), WriteError> { +pub async fn handle_unfriend_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> { + let jid: JID = jid.into(); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribe), @@ -340,11 +342,11 @@ pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result< pub async fn handle_delete_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -399,7 +401,7 @@ pub async fn handle_delete_contact<Fs: FileStore + Clone>( pub async fn handle_update_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, contact_update: ContactUpdate, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); @@ -410,7 +412,8 @@ pub async fn handle_update_contact<Fs: FileStore + Clone>( .map(|group| stanza::roster::Group(Some(group))), ); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + // TODO: these clones could technically be avoided? + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -474,7 +477,7 @@ pub async fn handle_set_status<Fs: FileStore + Clone>( Ok(()) } -pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: JID, body: Body) { +pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: BareJID, body: Body) { // upsert the chat and user the message will be delivered to. if there is a conflict, it will return whatever was there, otherwise it will return false by default. // let have_chatted = logic.db().upsert_chat_and_user(&jid).await.unwrap_or(false); let have_chatted = match logic.db().upsert_chat_and_user(jid.clone()).await { @@ -490,7 +493,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, let nick; let mark_chat_as_chatted; if have_chatted == false { - match logic.db.read_user(logic.bare_jid.clone()).await { + match logic.db.read_user(logic.jid.clone()).await { Ok(u) => { nick = u.nick.map(|nick| Nick(nick)); mark_chat_as_chatted = true; @@ -513,7 +516,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, let timestamp = Utc::now(); let message = Message { id, - from: connection.jid().as_bare(), + from: connection.jid().to_bare(), body: body.clone(), timestamp, delivery: Some(Delivery::Sending), @@ -532,12 +535,12 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, .await; } - let from = match logic.db().read_user(logic.bare_jid.clone()).await { + let from = match logic.db().read_user(logic.jid.clone()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: logic.bare_jid.clone(), + jid: logic.jid.clone(), nick: None, avatar: None, } @@ -548,7 +551,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, logic .update_sender() .send(UpdateMessage::Message { - to: jid.as_bare(), + to: jid.clone(), message, from, }) @@ -556,9 +559,9 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, // prepare the message stanza let message_stanza = Stanza::Message(stanza::client::message::Message { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: Some(id.to_string()), - to: Some(jid.clone()), + to: Some(jid.clone().into()), // TODO: specify message type r#type: stanza::client::message::MessageType::Chat, // TODO: lang ? @@ -639,7 +642,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( ) -> Result<Info, DiscoError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -667,7 +670,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -694,7 +697,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( } } else { Err(DiscoError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -710,7 +713,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( ) -> Result<Items, DiscoError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -736,7 +739,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -763,7 +766,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( } } else { Err(DiscoError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -828,7 +831,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( }, }; let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: None, r#type: IqType::Set, @@ -850,7 +853,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( // TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise. }) if r#type == IqType::Result || r#type == IqType::Error => { if from == None || - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) { match r#type { IqType::Result => { @@ -870,7 +873,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( } } else { Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -878,10 +881,11 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( } } -pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<JID>, node: String, id: String) -> Result<pep::Item, PEPError> { +pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<BareJID>, node: String, id: String) -> Result<pep::Item, PEPError> { + let jid = jid.map(|jid| Into::<JID>::into(jid)); let stanza_id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: stanza_id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -909,7 +913,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -955,7 +959,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, } else { // TODO: include expected entity Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -968,29 +972,33 @@ pub async fn handle_change_nick<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, Ok(()) } -pub async fn handle_change_avatar<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, img_data: Option<Vec<u8>>) -> Result<(), AvatarPublishError<Fs>> { +pub async fn handle_change_avatar<Fs: FileStore + Clone + 'static>(logic: &ClientLogic<Fs>, img_data: Option<Vec<u8>>) -> Result<(), AvatarPublishError<Fs>> { match img_data { // set avatar Some(data) => { - // load the image data and guess the format - let image = ImageReader::new(Cursor::new(data)).with_guessed_format()?.decode()?; + let (bytes, hash, data_png, data_b64) = spawn_blocking(move || -> Result<_, _> { + // load the image data and guess the format + let image = ImageReader::new(Cursor::new(data)).with_guessed_format()?.decode()?; + + // convert the image to png; + let mut data_png = Vec::new(); + let image = image.resize(192, 192, image::imageops::FilterType::Nearest); + image.write_to(&mut Cursor::new(&mut data_png), image::ImageFormat::Jpeg)?; - // convert the image to png; - let mut data_png = Vec::new(); - let image = image.resize(192, 192, image::imageops::FilterType::Nearest); - image.write_to(&mut Cursor::new(&mut data_png), image::ImageFormat::Jpeg)?; + // calculate the length of the data in bytes. + let bytes = data_png.len().try_into()?; - // calculate the length of the data in bytes. - let bytes = data_png.len().try_into()?; + // calculate sha1 hash of the data + let mut sha1 = Sha1::new(); + sha1.update(&data_png); + let sha1_result = sha1.finalize(); + let hash = hex::encode(sha1_result); - // calculate sha1 hash of the data - let mut sha1 = Sha1::new(); - sha1.update(&data_png); - let sha1_result = sha1.finalize(); - let hash = hex::encode(sha1_result); + // encode the image data as base64 + let data_b64 = BASE64_STANDARD.encode(data_png.clone()); - // encode the image data as base64 - let data_b64 = BASE64_STANDARD.encode(data_png.clone()); + Ok::<(u32, String, Vec<u8>, String), AvatarPublishError<Fs>>((bytes, hash, data_png, data_b64)) + }).await.unwrap()?; // publish the data to the data node logic.client().publish(pep::Item::AvatarData(Some(avatar::Data { hash: hash.clone(), data_b64 })), "urn:xmpp:avatar:data".to_string()).await?; @@ -1024,7 +1032,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( ) -> Result<(), PEPError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: None, r#type: IqType::Set, @@ -1046,7 +1054,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( // TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise. }) if r#type == IqType::Result || r#type == IqType::Error => { if from == None || - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) { match r#type { IqType::Result => { @@ -1067,7 +1075,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( } } else { Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -1076,7 +1084,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( } // TODO: could probably macro-ise? -pub async fn handle_online_result<Fs: FileStore + Clone>( +pub async fn handle_online_result<Fs: FileStore + Clone + 'static>( logic: &ClientLogic<Fs>, command: Command<Fs>, connection: Connected, diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs index 30d0830..67b0d3f 100644 --- a/filamento/src/logic/process_stanza.rs +++ b/filamento/src/logic/process_stanza.rs @@ -70,7 +70,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( // TODO: proper id xep .map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4())) .unwrap_or_else(|| Uuid::new_v4()), - from: from.as_bare(), + from: from.to_bare(), timestamp, body: Body { body: body.body.unwrap_or_default(), @@ -80,31 +80,34 @@ pub async fn recv_message<Fs: FileStore + Clone>( // TODO: process message type="error" // save the message to the database - match logic.db().upsert_chat_and_user(from.clone()).await { - Ok(_) => { - if let Err(e) = logic - .db() - .create_message_with_user_resource( - message.clone(), - from.clone(), - from.clone(), - ) - .await - { - error!("failed to create message: {}", e); + match logic.db().upsert_chat_and_user(from.to_bare()).await { + Ok(_) => match from.as_full() { + Ok(from) => { + if let Err(e) = logic + .db() + .create_message_with_user_resource( + message.clone(), + from.to_bare(), + from.clone(), + ) + .await + { + error!("failed to create message: {}", e); + } } - } + Err(e) => error!("failed to create message: {}", e), + }, Err(e) => { error!("failed to upsert chat and user: {}", e); } }; - let from_user = match logic.db().read_user(from.as_bare()).await { + let from_user = match logic.db().read_user(from.to_bare()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, avatar: None, } @@ -115,7 +118,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::Message { - to: from.as_bare(), + to: from.to_bare(), from: from_user, message, }) @@ -125,13 +128,13 @@ pub async fn recv_message<Fs: FileStore + Clone>( if let Some(nick) = stanza_message.nick { let nick = nick.0; if nick.is_empty() { - match logic.db().delete_user_nick(from.as_bare()).await { + match logic.db().delete_user_nick(from.to_bare()).await { Ok(changed) => { if changed { logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -145,7 +148,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -154,7 +157,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( } else { match logic .db() - .upsert_user_nick(from.as_bare(), nick.clone()) + .upsert_user_nick(from.to_bare(), nick.clone()) .await { Ok(changed) => { @@ -162,7 +165,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -176,7 +179,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -199,7 +202,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( if nick.is_empty() { match logic .db() - .delete_user_nick(from.as_bare()) + .delete_user_nick(from.to_bare()) .await { Ok(changed) => { @@ -207,7 +210,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -223,7 +226,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -233,7 +236,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( match logic .db() .upsert_user_nick( - from.as_bare(), + from.to_bare(), nick.clone(), ) .await @@ -243,7 +246,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -259,7 +262,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -294,7 +297,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( match logic .db() .upsert_user_avatar( - from.as_bare(), + from.to_bare(), metadata.id.clone(), ) .await @@ -323,7 +326,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( }) { Ok(false) => { // get data - let pep_item = logic.client().get_pep_item(Some(from.as_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?; + let pep_item = logic.client().get_pep_item(Some(from.to_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?; match pep_item { crate::pep::Item::AvatarData(data) => { let data = data.map(|data| data.data_b64).unwrap_or_default().replace("\n", ""); @@ -344,7 +347,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: Some( metadata.id.clone(), ), @@ -371,7 +374,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: Some( metadata.id.clone(), ), @@ -401,7 +404,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( // delete avatar match logic .db() - .delete_user_avatar(from.as_bare()) + .delete_user_avatar(from.to_bare()) .await { Ok((changed, old_avatar)) => { @@ -419,7 +422,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: None, }, ) @@ -488,6 +491,7 @@ pub async fn recv_presence( } stanza::client::presence::PresenceType::Subscribe => { // may get a subscription request from somebody who is not a contact!!! therefore should be its own kind of event + let from = from.try_into()?; Ok(Some(UpdateMessage::SubscriptionRequest(from))) } stanza::client::presence::PresenceType::Unavailable => { @@ -546,7 +550,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( iq: Iq, ) -> Result<Option<UpdateMessage>, IqProcessError> { if let Some(to) = &iq.to { - if *to == *connection.jid() { + // TODO: this clone could mayb b avoided + if *to == connection.jid().clone().into() { } else { return Err(IqProcessError::Iq(IqError::IncorrectAddressee(to.clone()))); } @@ -556,7 +561,9 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // TODO: maybe actually store the server in the connection again LOL + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); let id = iq.id.clone(); debug!("received iq result with id `{}` from {}", id, from); logic @@ -570,7 +577,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); if let Some(query) = iq.query { match query { stanza::client::iq::Query::DiscoInfo(query) => { @@ -594,7 +602,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } Err(_e) => { let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -614,7 +622,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( }, Err(_e) => { let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -634,7 +642,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } }; let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Result, @@ -653,7 +661,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( _ => { warn!("received unsupported iq get from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -677,7 +685,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { info!("received malformed iq query from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -698,7 +706,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); if let Some(query) = iq.query { match query { stanza::client::iq::Query::Roster(mut query) => { @@ -725,7 +734,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( .await; } let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Result, @@ -751,7 +760,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { warn!("received malformed roster push"); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -771,7 +780,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( _ => { warn!("received unsupported iq set from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -791,7 +800,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { warn!("received malformed iq set from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -820,7 +829,7 @@ pub async fn process_stanza<Fs: FileStore + Clone>( Stanza::Presence(presence) => Ok(recv_presence(presence).await?), Stanza::Iq(iq) => Ok(recv_iq(logic, connection.clone(), iq).await?), // unreachable, always caught by lampada - // TODO: make cleaner than this in some way + // TODO: make cleaner than this in some way, by just reexporting a stanza enum from lampada ig. Stanza::Error(error) => { unreachable!() } diff --git a/filamento/src/roster.rs b/filamento/src/roster.rs index 6b61e10..0498278 100644 --- a/filamento/src/roster.rs +++ b/filamento/src/roster.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, fmt::Display}; -use jid::JID; +use jid::BareJID; use rusqlite::{ ToSql, types::{FromSql, ToSqlOutput, Value}, @@ -15,7 +15,7 @@ pub struct ContactUpdate { #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct Contact { // jid is the id used to reference everything, but not the primary key - pub user_jid: JID, + pub user_jid: BareJID, pub subscription: Subscription, /// client user defined name pub name: Option<String>, diff --git a/filamento/src/user.rs b/filamento/src/user.rs index f30933c..f962a4c 100644 --- a/filamento/src/user.rs +++ b/filamento/src/user.rs @@ -1,10 +1,10 @@ -use jid::JID; +use jid::BareJID; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct User { - pub jid: JID, + pub jid: BareJID, pub nick: Option<String>, pub avatar: Option<String>, // pub cached_status_message: Option<String>, diff --git a/lampada/src/connection/mod.rs b/lampada/src/connection/mod.rs index 3a3187f..51c3758 100644 --- a/lampada/src/connection/mod.rs +++ b/lampada/src/connection/mod.rs @@ -7,7 +7,7 @@ use std::{ time::Duration, }; -use jid::JID; +use jid::{BareJID, FullJID, JID}; use luz::jabber_stream::bound_stream::BoundJabberStream; use read::{ReadControl, ReadControlHandle, ReadState}; use stanza::{client::Stanza, stream_error::Error as StreamError}; @@ -121,12 +121,11 @@ where break } - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); + let mut jid = self.connected.jid.clone().into(); // TODO: make sure connect_and_login does not modify the jid, but instead returns a jid. or something like that - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -169,12 +168,11 @@ where else => break, }; - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); + let mut jid = self.connected.jid.clone().into(); // TODO: same here - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -214,11 +212,10 @@ where else => break, }; - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let mut jid = self.connected.jid.clone().into(); + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -305,8 +302,7 @@ impl SupervisorHandle { pub fn new<Lgc>( streams: BoundJabberStream, on_crash: oneshot::Sender<()>, - jid: JID, - server: JID, + jid: FullJID, password: Arc<String>, logic: Lgc, ) -> (WriteHandle, Self) @@ -327,7 +323,6 @@ impl SupervisorHandle { let connected = Connected { jid, write_handle: write_handle.clone(), - server, }; let supervisor_sender = SupervisorSender { diff --git a/lampada/src/error.rs b/lampada/src/error.rs index f29d0cc..a5af3a2 100644 --- a/lampada/src/error.rs +++ b/lampada/src/error.rs @@ -24,7 +24,7 @@ pub enum ConnectionError { #[error("disconnected")] Disconnected, #[error("invalid server jid: {0}")] - InvalidServerJID(#[from] jid::ParseError), + InvalidServerJID(#[from] jid::JIDError), } #[derive(Debug, Error, Clone)] diff --git a/lampada/src/lib.rs b/lampada/src/lib.rs index 6b6cbe8..d1d451e 100644 --- a/lampada/src/lib.rs +++ b/lampada/src/lib.rs @@ -12,6 +12,7 @@ pub use connection::write::WriteMessage; pub use connection::SupervisorSender; use error::ConnectionError; use futures::{future::Fuse, FutureExt}; +use jid::{BareJID, FullJID}; use luz::JID; use stanza::client::{ iq::{self, Iq, IqType}, @@ -36,24 +37,18 @@ pub mod error; #[derive(Clone)] pub struct Connected { // full jid will stay stable across reconnections - jid: JID, + jid: FullJID, write_handle: WriteHandle, - // the server jid - server: JID, } impl Connected { - pub fn jid(&self) -> &JID { + pub fn jid(&self) -> &FullJID { &self.jid } pub fn write_handle(&self) -> &WriteHandle { &self.write_handle } - - pub fn server(&self) -> &JID { - &self.server - } } /// everything that a particular xmpp client must implement @@ -227,34 +222,17 @@ where .await; } None => { - let mut jid = self.jid.clone(); - let mut domain = jid.domainpart.clone(); // TODO: check what happens upon reconnection with same resource (this is probably what one wants to do and why jid should be mutated from a bare jid to one with a resource) let streams_result = - luz::connect_and_login(&mut jid, &*self.password, &mut domain) - .await; - let server: JID = match domain.parse() { - Ok(j) => j, - Err(e) => { - self.logic - .clone() - .handle_connection_error(ConnectionError::InvalidServerJID( - e.clone(), - )) - .await; - sender.send(Err(ConnectionError::InvalidServerJID(e))); - continue; - } - }; + luz::connect_and_login(&self.jid, &*self.password).await; match streams_result { - Ok(s) => { + Ok((s, full_jid)) => { debug!("ok stream result"); let (shutdown_send, shutdown_recv) = oneshot::channel::<()>(); let (writer, supervisor) = SupervisorHandle::new( s, shutdown_send, - jid.clone(), - server.clone(), + full_jid.clone(), self.password.clone(), self.logic.clone(), ); @@ -262,18 +240,16 @@ where let shutdown_recv = shutdown_recv.fuse(); self.connection_supervisor_shutdown = shutdown_recv; - let resource = jid.resourcepart.clone().expect("client somehow connected without binding"); let connected = Connected { - jid, + jid: full_jid.clone(), write_handle: writer, - server, }; self.logic.clone().handle_connect(connected.clone()).await; self.connected = Some((connected, supervisor)); // REMEMBER TO NOTIFY IT@S GOOD - sender.send(Ok(resource)); + sender.send(Ok(full_jid.resourcepart)); } Err(e) => { tracing::error!("error: {}", e); diff --git a/luz/src/client/tcp.rs b/luz/src/client/tcp.rs index 4e35ef0..a30eccc 100644 --- a/luz/src/client/tcp.rs +++ b/luz/src/client/tcp.rs @@ -1,3 +1,4 @@ +use jid::FullJID; use rsasl::config::SASLConfig; use stanza::{ sasl::Mechanisms, @@ -10,22 +11,25 @@ use crate::{ }; pub async fn connect_and_login( - jid: &mut JID, + jid: &JID, + // jid: &mut JID, password: impl AsRef<str>, - server: &mut String, -) -> Result<BoundJabberStream> { + // server: &mut String, +) -> Result<(BoundJabberStream, FullJID)> { let auth = SASLConfig::with_credentials( None, jid.localpart.clone().ok_or(Error::NoLocalpart)?, password.as_ref().to_string(), ) .map_err(|e| Error::SASL(e.into()))?; + let mut server = jid.domainpart.clone(); let mut conn_state = Connecting::start(&server).await?; loop { match conn_state { Connecting::InsecureConnectionEstablised(tcp_stream) => { conn_state = Connecting::InsecureStreamStarted( - JabberStream::start_stream(Connection::Unencrypted(tcp_stream), server).await?, + JabberStream::start_stream(Connection::Unencrypted(tcp_stream), &mut server) + .await?, ) } Connecting::InsecureStreamStarted(jabber_stream) => { @@ -46,8 +50,9 @@ pub async fn connect_and_login( )) } Connecting::ConnectionEstablished(connection) => { - conn_state = - Connecting::StreamStarted(JabberStream::start_stream(connection, server).await?) + conn_state = Connecting::StreamStarted( + JabberStream::start_stream(connection, &mut server).await?, + ) } Connecting::StreamStarted(jabber_stream) => { conn_state = Connecting::GotFeatures(jabber_stream.get_features().await?) @@ -68,7 +73,7 @@ pub async fn connect_and_login( ) } Connecting::Bind(jabber_stream) => { - return Ok(jabber_stream.bind(jid).await?.to_bound_jabber()); + return Ok(jabber_stream.bind(jid).await?); } } } diff --git a/luz/src/client/ws.rs b/luz/src/client/ws.rs index 13c3cdf..0ad8d0e 100644 --- a/luz/src/client/ws.rs +++ b/luz/src/client/ws.rs @@ -1,3 +1,4 @@ +use jid::FullJID; use rsasl::config::SASLConfig; use stanza::{ sasl::Mechanisms, @@ -9,22 +10,22 @@ use crate::{ }; pub async fn connect_and_login( - jid: &mut JID, + jid: &JID, password: impl AsRef<str>, - server: &mut String, -) -> Result<BoundJabberStream> { +) -> Result<(BoundJabberStream, FullJID)> { let auth = SASLConfig::with_credentials( None, jid.localpart.clone().ok_or(Error::NoLocalpart)?, password.as_ref().to_string(), ) .map_err(|e| Error::SASL(e.into()))?; + let mut server = jid.domainpart.clone(); let mut conn_state = Connecting::start(&server).await?; loop { match conn_state { Connecting::ConnectionEstablished(ws) => { conn_state = - Connecting::StreamStarted(JabberStream::start_stream(ws, server).await?) + Connecting::StreamStarted(JabberStream::start_stream(ws, &mut server).await?) } Connecting::StreamStarted(jabber_stream) => { conn_state = Connecting::GotFeatures(jabber_stream.get_features().await?) @@ -45,7 +46,7 @@ pub async fn connect_and_login( ) } Connecting::Bind(jabber_stream) => { - return Ok(jabber_stream.bind(jid).await?.to_bound_jabber()); + return Ok(jabber_stream.bind(jid).await?); } } } diff --git a/luz/src/connection/tcp.rs b/luz/src/connection/tcp.rs index a9e81c3..7409a47 100644 --- a/luz/src/connection/tcp.rs +++ b/luz/src/connection/tcp.rs @@ -96,7 +96,7 @@ impl Connection { // } pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> { - let jid: JID = JID::from_str(jid.as_ref())?; + let jid: JID = JID::from_str(jid.as_ref()).map_err(|e| Error::JID(e.into()))?; let server = jid.domainpart.clone(); Self::connect(&server).await } diff --git a/luz/src/error.rs b/luz/src/error.rs index fcb32a0..d9b2930 100644 --- a/luz/src/error.rs +++ b/luz/src/error.rs @@ -1,7 +1,7 @@ use std::str::Utf8Error; use std::sync::Arc; -use jid::ParseError; +use jid::JIDError; use rsasl::mechname::MechanismNameError; use stanza::client::error::Error as ClientError; use stanza::sasl::Failure; @@ -36,7 +36,7 @@ pub enum Error { #[error("sasl error: {0}")] SASL(#[from] SASLError), #[error("jid error: {0}")] - JID(#[from] ParseError), + JID(#[from] JIDError), #[error("client stanza error: {0}")] ClientError(#[from] ClientError), #[error("stream error: {0}")] diff --git a/luz/src/jabber_stream.rs b/luz/src/jabber_stream.rs index f77e6a9..490a0f7 100644 --- a/luz/src/jabber_stream.rs +++ b/luz/src/jabber_stream.rs @@ -7,7 +7,8 @@ mod ws; use std::str::{self, FromStr}; use std::sync::Arc; -use jid::JID; +use bound_stream::BoundJabberStream; +use jid::{FullJID, JID}; use peanuts::IntoElement; #[cfg(target_arch = "wasm32")] use peanuts::WebSocketOnMessageRead; @@ -147,9 +148,9 @@ impl JabberStream { } #[instrument] - pub async fn bind(mut self, jid: &mut JID) -> Result<Self> { + pub async fn bind(mut self, jid: &JID) -> Result<(BoundJabberStream, FullJID)> { let iq_id = uuid::Uuid::new_v4().to_string(); - if let Some(resource) = &jid.resourcepart { + if let Some(resource) = &jid.resourcepart() { let iq = Iq { from: None, id: iq_id.clone(), @@ -176,8 +177,7 @@ impl JabberStream { })), errors: _, } if id == iq_id => { - *jid = new_jid; - return Ok(self); + return Ok((self.to_bound_jabber(), new_jid)); } Iq { from: _, @@ -219,8 +219,7 @@ impl JabberStream { })), errors: _, } if id == iq_id => { - *jid = new_jid; - return Ok(self); + return Ok((self.to_bound_jabber(), new_jid)); } Iq { from: _, @@ -257,7 +256,11 @@ impl JabberStream { id: None, lang: None, version: Some("1.0".to_string()), - to: Some(JID::from_str(server.as_ref())?), + to: Some( + JID::from_str(server.as_ref()) + .map_err(|e| Error::JID(e.into()))? + .try_into()?, + ), }; writer.write(&open).await?; @@ -290,7 +293,9 @@ impl JabberStream { // opening stream element let stream = Stream::new_client( None, - JID::from_str(server.as_ref())?, + JID::from_str(server.as_ref()) + .map_err(|e| Error::JID(e.into()))? + .try_into()?, None, "en".to_string(), ); diff --git a/stanza/Cargo.toml b/stanza/Cargo.toml index 884584a..cd79cee 100644 --- a/stanza/Cargo.toml +++ b/stanza/Cargo.toml @@ -24,5 +24,8 @@ xep_0156 = ["dep:chrono"] xep_0172 = [] xep_0199 = [] xep_0203 = ["dep:chrono"] +xep_0280 = ["xep_0297"] +xep_0297 = ["xep_0203"] xep_0300 = [] +xep_0334 = [] xep_0390 = ["xep_0300"] diff --git a/stanza/src/bind.rs b/stanza/src/bind.rs index 3ce2246..0f0f681 100644 --- a/stanza/src/bind.rs +++ b/stanza/src/bind.rs @@ -1,4 +1,4 @@ -use jid::JID; +use jid::FullJID; use peanuts::{Element, FromElement, IntoElement}; pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; @@ -54,7 +54,7 @@ impl IntoElement for BindType { // minLength 8 maxLength 3071 #[derive(Clone, Debug)] -pub struct FullJidType(pub JID); +pub struct FullJidType(pub FullJID); impl FromElement for FullJidType { fn from_element(mut element: peanuts::Element) -> peanuts::DeserializeResult<Self> { diff --git a/stanza/src/client/iq.rs b/stanza/src/client/iq.rs index 478530a..f5cdf32 100644 --- a/stanza/src/client/iq.rs +++ b/stanza/src/client/iq.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use jid::JID; use peanuts::{DeserializeError, Element, FromElement, IntoElement, XML_NS}; +#[cfg(feature = "xep_0280")] +use crate::xep_0280::{self, Disable, Enable}; use crate::{ bind::{self, Bind}, client::error::Error, @@ -53,6 +55,10 @@ pub enum Query { Ping(Ping), #[cfg(feature = "rfc_6121")] Roster(roster::Query), + #[cfg(feature = "xep_0280")] + CarbonsEnable(Enable), + #[cfg(feature = "xep_0280")] + CarbonsDisable(Disable), Unsupported, } @@ -80,6 +86,14 @@ impl FromElement for Query { (Some(xep_0060::owner::XMLNS), "pubsub") => Ok(Query::PubsubOwner( xep_0060::owner::Pubsub::from_element(element)?, )), + #[cfg(feature = "xep_0280")] + (Some(xep_0280::XMLNS), "enable") => { + Ok(Query::CarbonsEnable(Enable::from_element(element)?)) + } + #[cfg(feature = "xep_0280")] + (Some(xep_0280::XMLNS), "disable") => { + Ok(Query::CarbonsDisable(Disable::from_element(element)?)) + } _ => Ok(Query::Unsupported), } } @@ -103,6 +117,10 @@ impl IntoElement for Query { Query::Pubsub(pubsub) => pubsub.builder(), #[cfg(feature = "xep_0060")] Query::PubsubOwner(pubsub) => pubsub.builder(), + #[cfg(feature = "xep_0280")] + Query::CarbonsEnable(enable) => enable.builder(), + #[cfg(feature = "xep_0280")] + Query::CarbonsDisable(disable) => disable.builder(), } } } diff --git a/stanza/src/client/message.rs b/stanza/src/client/message.rs index 41761d2..3a7be9a 100644 --- a/stanza/src/client/message.rs +++ b/stanza/src/client/message.rs @@ -11,6 +11,12 @@ use crate::xep_0131::Headers; use crate::xep_0172::Nick; #[cfg(feature = "xep_0203")] use crate::xep_0203::Delay; +#[cfg(feature = "xep_0280")] +use crate::xep_0280::{Private, Received, Sent}; +#[cfg(feature = "xep_0297")] +use crate::xep_0297::Forwarded; +#[cfg(feature = "xep_0334")] +use crate::xep_0334::{NoCopy, NoPermanentStore, NoStore, Store}; use super::XMLNS; @@ -34,6 +40,22 @@ pub struct Message { pub nick: Option<Nick>, #[cfg(feature = "xep_0060")] pub event: Option<Event>, + #[cfg(feature = "xep_0297")] + pub forwarded: Option<Forwarded>, + #[cfg(feature = "xep_0280")] + pub sent: Option<Sent>, + #[cfg(feature = "xep_0280")] + pub received: Option<Received>, + #[cfg(feature = "xep_0280")] + pub private: Option<Private>, + #[cfg(feature = "xep_0334")] + pub no_permanent_store: Option<NoPermanentStore>, + #[cfg(feature = "xep_0334")] + pub no_store: Option<NoStore>, + #[cfg(feature = "xep_0334")] + pub no_copy: Option<NoCopy>, + #[cfg(feature = "xep_0334")] + pub store: Option<Store>, } impl FromElement for Message { @@ -63,6 +85,30 @@ impl FromElement for Message { #[cfg(feature = "xep_0060")] let event = element.child_opt()?; + #[cfg(feature = "xep_0297")] + let forwarded = element.child_opt()?; + + #[cfg(feature = "xep_0280")] + let sent = element.child_opt()?; + + #[cfg(feature = "xep_0280")] + let received = element.child_opt()?; + + #[cfg(feature = "xep_0280")] + let private = element.child_opt()?; + + #[cfg(feature = "xep_0334")] + let no_permanent_store = element.child_opt()?; + + #[cfg(feature = "xep_0334")] + let no_store = element.child_opt()?; + + #[cfg(feature = "xep_0334")] + let no_copy = element.child_opt()?; + + #[cfg(feature = "xep_0334")] + let store = element.child_opt()?; + Ok(Message { from, id, @@ -80,6 +126,22 @@ impl FromElement for Message { nick, #[cfg(feature = "xep_0060")] event, + #[cfg(feature = "xep_0297")] + forwarded, + #[cfg(feature = "xep_0280")] + sent, + #[cfg(feature = "xep_0280")] + received, + #[cfg(feature = "xep_0280")] + private, + #[cfg(feature = "xep_0334")] + no_permanent_store, + #[cfg(feature = "xep_0334")] + no_store, + #[cfg(feature = "xep_0334")] + no_copy, + #[cfg(feature = "xep_0334")] + store, }) } } @@ -114,6 +176,30 @@ impl IntoElement for Message { #[cfg(feature = "xep_0060")] let builder = builder.push_child_opt(self.event.clone()); + #[cfg(feature = "xep_0297")] + let builder = builder.push_child_opt(self.forwarded.clone()); + + #[cfg(feature = "xep_0280")] + let builder = builder.push_child_opt(self.sent.clone()); + + #[cfg(feature = "xep_0280")] + let builder = builder.push_child_opt(self.received.clone()); + + #[cfg(feature = "xep_0280")] + let builder = builder.push_child_opt(self.private.clone()); + + #[cfg(feature = "xep_0334")] + let builder = builder.push_child_opt(self.no_permanent_store); + + #[cfg(feature = "xep_0334")] + let builder = builder.push_child_opt(self.no_store); + + #[cfg(feature = "xep_0334")] + let builder = builder.push_child_opt(self.no_copy); + + #[cfg(feature = "xep_0334")] + let builder = builder.push_child_opt(self.store); + builder } } diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs index 8f8d430..4cb62a3 100644 --- a/stanza/src/lib.rs +++ b/stanza/src/lib.rs @@ -33,8 +33,14 @@ pub mod xep_0172; pub mod xep_0199; #[cfg(feature = "xep_0203")] pub mod xep_0203; +#[cfg(feature = "xep_0280")] +pub mod xep_0280; +#[cfg(feature = "xep_0297")] +pub mod xep_0297; #[cfg(feature = "xep_0300")] pub mod xep_0300; +#[cfg(feature = "xep_0334")] +pub mod xep_0334; #[cfg(feature = "xep_0390")] pub mod xep_0390; diff --git a/stanza/src/rfc_7395.rs b/stanza/src/rfc_7395.rs index 64d9f70..73e947d 100644 --- a/stanza/src/rfc_7395.rs +++ b/stanza/src/rfc_7395.rs @@ -1,12 +1,12 @@ -use jid::JID; +use jid::BareJID; use peanuts::{Element, ElementBuilder, FromElement, IntoElement}; pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-framing"; #[derive(Debug)] pub struct Open { - pub from: Option<JID>, - pub to: Option<JID>, + pub from: Option<BareJID>, + pub to: Option<BareJID>, pub id: Option<String>, pub version: Option<String>, pub lang: Option<String>, @@ -46,8 +46,8 @@ impl IntoElement for Open { #[derive(Debug, Default)] pub struct Close { - pub from: Option<JID>, - pub to: Option<JID>, + pub from: Option<BareJID>, + pub to: Option<BareJID>, pub id: Option<String>, pub version: Option<String>, pub lang: Option<String>, diff --git a/stanza/src/roster.rs b/stanza/src/roster.rs index 14f65ef..dcbf017 100644 --- a/stanza/src/roster.rs +++ b/stanza/src/roster.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use jid::JID; +use jid::BareJID; use peanuts::{DeserializeError, Element, FromElement, IntoElement}; pub const XMLNS: &str = "jabber:iq:roster"; @@ -38,7 +38,7 @@ pub struct Item { /// signals subscription sub-states (server only) pub ask: bool, /// uniquely identifies item - pub jid: JID, + pub jid: BareJID, /// handle that is determined by user, not contact pub name: Option<String>, /// state of the presence subscription diff --git a/stanza/src/stream.rs b/stanza/src/stream.rs index 5be235a..e2f4f9b 100644 --- a/stanza/src/stream.rs +++ b/stanza/src/stream.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use jid::JID; +use jid::BareJID; use peanuts::{Element, ElementBuilder, FromElement, IntoElement}; use thiserror::Error; @@ -18,8 +18,8 @@ pub const XMLNS: &str = "http://etherx.jabber.org/streams"; // #[peanuts(xmlns = XMLNS)] #[derive(Debug)] pub struct Stream { - pub from: Option<JID>, - to: Option<JID>, + pub from: Option<BareJID>, + to: Option<BareJID>, id: Option<String>, version: Option<String>, // TODO: lang enum @@ -64,8 +64,8 @@ impl IntoElement for Stream { impl<'s> Stream { pub fn new( - from: Option<JID>, - to: Option<JID>, + from: Option<BareJID>, + to: Option<BareJID>, id: Option<String>, version: Option<String>, lang: Option<String>, @@ -81,7 +81,12 @@ impl<'s> Stream { /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute. /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection. - pub fn new_client(from: Option<JID>, to: JID, id: Option<String>, lang: String) -> Self { + pub fn new_client( + from: Option<BareJID>, + to: BareJID, + id: Option<String>, + lang: String, + ) -> Self { Self { from, to: Some(to), diff --git a/stanza/src/xep_0060/owner.rs b/stanza/src/xep_0060/owner.rs index 0617712..4876bf5 100644 --- a/stanza/src/xep_0060/owner.rs +++ b/stanza/src/xep_0060/owner.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use jid::JID; +use jid::{BareJID, JID}; use peanuts::{DeserializeError, Element, FromElement, IntoElement}; use crate::xep_0004::X; @@ -85,7 +85,7 @@ impl IntoElement for Affiliations { #[derive(Clone, Debug)] pub struct Affiliation { affiliation: AffiliationType, - jid: JID, + jid: BareJID, } impl FromElement for Affiliation { diff --git a/stanza/src/xep_0280.rs b/stanza/src/xep_0280.rs new file mode 100644 index 0000000..c26793f --- /dev/null +++ b/stanza/src/xep_0280.rs @@ -0,0 +1,109 @@ +use peanuts::{Element, FromElement, IntoElement}; + +use crate::xep_0297::Forwarded; + +pub const XMLNS: &str = "urn:xmpp:carbons:2"; + +#[derive(Clone, Debug)] +pub struct Disable; + +impl FromElement for Disable { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("disable")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for Disable { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("disable", Some(XMLNS)) + } +} + +#[derive(Clone, Debug)] +pub struct Enable; + +impl FromElement for Enable { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("enable")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for Enable { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("enable", Some(XMLNS)) + } +} + +#[derive(Clone, Debug)] +pub struct Private; + +impl FromElement for Private { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("private")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for Private { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("private", Some(XMLNS)) + } +} + +#[derive(Clone, Debug)] +pub struct Received { + forwarded: Forwarded, +} + +impl FromElement for Received { + fn from_element(mut element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("received")?; + element.check_namespace(XMLNS)?; + + let forwarded = element.pop_child_one()?; + + Ok(Self { forwarded }) + } +} + +impl IntoElement for Received { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("received", Some(XMLNS)).push_child(self.forwarded.clone()) + } +} + +#[derive(Clone, Debug)] +pub struct Sent { + forwarded: Forwarded, +} + +impl FromElement for Sent { + fn from_element(mut element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("sent")?; + element.check_namespace(XMLNS)?; + + let forwarded = element.pop_child_one()?; + + Ok(Self { forwarded }) + } +} + +impl IntoElement for Sent { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("sent", Some(XMLNS)).push_child(self.forwarded.clone()) + } +} diff --git a/stanza/src/xep_0297.rs b/stanza/src/xep_0297.rs new file mode 100644 index 0000000..4dc8a26 --- /dev/null +++ b/stanza/src/xep_0297.rs @@ -0,0 +1,69 @@ +use peanuts::{Element, FromElement, IntoElement}; + +use crate::{ + client::{self, iq::Iq, message::Message, presence::Presence}, + xep_0203::Delay, +}; + +pub const XMLNS: &str = "urn:xmpp:forward:0"; + +#[derive(Clone, Debug)] +pub struct Forwarded { + delay: Option<Delay>, + stanza: Option<Box<Stanza>>, +} + +impl FromElement for Forwarded { + fn from_element(mut element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("forwarded")?; + element.check_namespace(XMLNS)?; + + let delay = element.pop_child_opt()?; + let stanza = element.pop_child_opt()?; + let stanza = stanza.map(|stanza| Box::new(stanza)); + + Ok(Self { delay, stanza }) + } +} + +impl IntoElement for Forwarded { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("forwarded", Some(XMLNS)) + .push_child_opt(self.delay.clone()) + .push_child_opt(self.stanza.clone().map(|stanza| *stanza)) + } +} + +#[derive(Clone, Debug)] +pub enum Stanza { + Message(Message), + Presence(Presence), + Iq(Iq), + // TODO: raw elements are received with reads. + // Raw(Element), +} + +impl FromElement for Stanza { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + match element.identify() { + (Some(client::XMLNS), "message") => { + Ok(Stanza::Message(Message::from_element(element)?)) + } + (Some(client::XMLNS), "presence") => { + Ok(Stanza::Presence(Presence::from_element(element)?)) + } + (Some(client::XMLNS), "iq") => Ok(Stanza::Iq(Iq::from_element(element)?)), + _ => Err(peanuts::DeserializeError::UnexpectedElement(element)), + } + } +} + +impl IntoElement for Stanza { + fn builder(&self) -> peanuts::ElementBuilder { + match self { + Stanza::Message(message) => message.builder(), + Stanza::Presence(presence) => presence.builder(), + Stanza::Iq(iq) => iq.builder(), + } + } +} diff --git a/stanza/src/xep_0334.rs b/stanza/src/xep_0334.rs new file mode 100644 index 0000000..9667ad7 --- /dev/null +++ b/stanza/src/xep_0334.rs @@ -0,0 +1,83 @@ +use peanuts::{Element, FromElement, IntoElement}; + +pub const XMLNS: &str = "urn:xmpp:hints"; + +#[derive(Clone, Copy, Debug)] +pub struct NoPermanentStore; + +impl FromElement for NoPermanentStore { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("no-permanent-store")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for NoPermanentStore { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("no-permanent-store", Some(XMLNS)) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NoStore; + +impl FromElement for NoStore { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("no-store")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for NoStore { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("no-store", Some(XMLNS)) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NoCopy; + +impl FromElement for NoCopy { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("no-copy")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for NoCopy { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("no-copy", Some(XMLNS)) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Store; + +impl FromElement for Store { + fn from_element(element: Element) -> peanuts::DeserializeResult<Self> { + element.check_name("store")?; + element.check_namespace(XMLNS)?; + + element.no_more_content()?; + + Ok(Self) + } +} + +impl IntoElement for Store { + fn builder(&self) -> peanuts::ElementBuilder { + Element::builder("store", Some(XMLNS)) + } +} |