aboutsummaryrefslogtreecommitdiffstats
path: root/filamento/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'filamento/src/lib.rs')
-rw-r--r--filamento/src/lib.rs249
1 files changed, 203 insertions, 46 deletions
diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs
index 14b0cae..40a2867 100644
--- a/filamento/src/lib.rs
+++ b/filamento/src/lib.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
@@ -16,7 +20,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},
@@ -32,6 +36,8 @@ use tokio::{
sync::{Mutex, mpsc, oneshot},
time::timeout,
};
+#[cfg(target_arch = "wasm32")]
+use tokio_with_wasm::alias as tokio;
use tracing::{debug, info};
use user::User;
use uuid::Uuid;
@@ -66,42 +72,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(
+ 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
@@ -115,7 +134,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
@@ -141,7 +160,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>>,
@@ -166,7 +185,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,
@@ -175,22 +194,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>,
},
}
@@ -251,13 +271,14 @@ impl<Fs: FileStore + Clone + Send + Sync + 'static> Client<Fs> {
let client = Self {
sender: command_sender,
// TODO: configure timeout
- timeout: Duration::from_secs(10),
+ 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);
+
tokio::spawn(async move { actor.run().await });
(client, update_recv)
@@ -265,9 +286,17 @@ impl<Fs: FileStore + Clone + Send + Sync + 'static> Client<Fs> {
}
impl<Fs: FileStore> Client<Fs> {
- pub async fn connect(&self) -> Result<(), ActorError> {
- self.send(CoreClientCommand::Connect).await?;
- Ok(())
+ /// returns the resource
+ pub async fn connect(&self) -> Result<String, CommandError<ConnectionError>> {
+ let (send, recv) = oneshot::channel::<Result<String, ConnectionError>>();
+ self.send(CoreClientCommand::Connect(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)))??;
+ Ok(result)
}
pub async fn disconnect(&self, offline: Offline) -> Result<(), ActorError> {
@@ -359,7 +388,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
@@ -371,9 +400,38 @@ impl<Fs: FileStore> Client<Fs> {
Ok(chat)
}
+ 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)
+ .await
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??;
+ Ok(result)
+ }
+
+ pub async fn get_message(&self, id: Uuid) -> Result<Message, CommandError<DatabaseError>> {
+ let (send, recv) = oneshot::channel();
+ self.send(CoreClientCommand::Command(Command::GetMessage(id, send)))
+ .await
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?;
+ let message = timeout(self.timeout, recv)
+ .await
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??;
+ Ok(message)
+ }
+
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)))
@@ -388,7 +446,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(
@@ -403,7 +461,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
@@ -427,7 +485,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
@@ -439,7 +497,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
@@ -451,7 +509,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
@@ -463,7 +521,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,
@@ -477,7 +538,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,
@@ -493,7 +557,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(
@@ -508,7 +572,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,
@@ -522,7 +589,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,
@@ -536,7 +603,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,
@@ -550,7 +617,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,
@@ -566,7 +633,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();
@@ -594,7 +661,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(())
@@ -673,7 +740,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>> {
@@ -728,3 +796,92 @@ impl<Fs: FileStore> From<Command<Fs>> for CoreClientCommand<Command<Fs>> {
CoreClientCommand::Command(value)
}
}
+
+#[cfg(test)]
+mod tests {
+ use wasm_bindgen_test::*;
+
+ use super::*;
+
+ wasm_bindgen_test_configure!(run_in_browser);
+
+ use crate::chat;
+ use crate::files::FilesMem;
+ use tokio_with_wasm::alias as tokio;
+
+ #[wasm_bindgen_test]
+ async fn login() -> () {
+ tracing_wasm::set_as_global_default();
+ let db = Db::create_connect_and_migrate("./filamento.db")
+ .await
+ .unwrap();
+ let (client, mut recv) = Client::new(
+ "test@blos.sm/testing2".try_into().unwrap(),
+ "slayed".to_string(),
+ db,
+ FilesMem::new(),
+ );
+
+ tokio::spawn(async move {
+ while let Some(msg) = recv.recv().await {
+ info!("{:#?}", msg)
+ }
+ });
+
+ client.connect().await.unwrap();
+ // tokio::time::sleep(Duration::from_secs(5)).await;
+ info!("changing nick");
+ client
+ .change_nick(Some("britney".to_string()))
+ .await
+ .unwrap();
+ let mut data = include_bytes!("../files/britney_starbies.jpg");
+ client.change_avatar(Some(data.to_vec())).await.unwrap();
+ info!("sending message");
+ client
+ .send_message(
+ BareJID::from_str("cel@blos.sm").unwrap(),
+ chat::Body {
+ body: "hallo!!!".to_string(),
+ },
+ )
+ .await
+ .unwrap();
+ info!("sent message");
+ client
+ .send_message(
+ BareJID::from_str("cel@blos.sm").unwrap(),
+ chat::Body {
+ body: "hallo 2".to_string(),
+ },
+ )
+ .await
+ .unwrap();
+ // tokio::time::sleep(Duration::from_secs(15)).await;
+ // info!("sending disco query");
+ // let info = client.disco_info(None, None).await.unwrap();
+ // info!("got disco result: {:#?}", info);
+ // let items = client.disco_items(None, None).await.unwrap();
+ // info!("got disco result: {:#?}", items);
+ // let info = client
+ // .disco_info(Some("blos.sm".parse().unwrap()), None)
+ // .await
+ // .unwrap();
+ // info!("got disco result: {:#?}", info);
+ // let items = client
+ // .disco_items(Some("blos.sm".parse().unwrap()), None)
+ // .await
+ // .unwrap();
+ // info!("got disco result: {:#?}", items);
+ // let info = client
+ // .disco_info(Some("pubsub.blos.sm".parse().unwrap()), None)
+ // .await
+ // .unwrap();
+ // info!("got disco result: {:#?}", info);
+ // let items = client
+ // .disco_items(Some("pubsub.blos.sm".parse().unwrap()), None)
+ // .await
+ // .unwrap();
+ // info!("got disco result: {:#?}", items); let mut jid: JID = "test@blos.sm".try_into().unwrap();
+ }
+}