Compare commits
No commits in common. "05b0d38490a69d058cdd0ee7b17140634d116af2" and "ec41f1d4ff07a00223b6ed34fc5b65c38d3cd535" have entirely different histories.
05b0d38490
...
ec41f1d4ff
|
@ -73,7 +73,6 @@ file/media sharing (further research needed):
|
||||||
- [ ] xep-0234: jingle file transfer
|
- [ ] xep-0234: jingle file transfer
|
||||||
|
|
||||||
need more research:
|
need more research:
|
||||||
- [ ] xep-0154: user profile
|
|
||||||
- [ ] message editing
|
- [ ] message editing
|
||||||
- [ ] xep-0308: last message correction (should not be used for older than last message according to spec)
|
- [ ] xep-0308: last message correction (should not be used for older than last message according to spec)
|
||||||
- [ ] chat read markers
|
- [ ] chat read markers
|
||||||
|
|
42
TODO.md
42
TODO.md
|
@ -2,36 +2,22 @@
|
||||||
|
|
||||||
## next
|
## next
|
||||||
|
|
||||||
feat(luz): everything in rfc6120 and rfc6121
|
ci/cd: doc generation
|
||||||
feat(luz): handle_online
|
feature: error handling on stream according to rfc6120
|
||||||
feat(luz): handle_offline
|
docs: jid
|
||||||
feat(luz): handle_stanza
|
docs: jabber
|
||||||
feat(luz): database
|
docs: starttls
|
||||||
feat(luz): error handling on stream according to rfc6120
|
docs: sasl
|
||||||
feat(luz): send message
|
docs: resource binding
|
||||||
feat(luz): receive message
|
|
||||||
feat(luz): retreive messages stored in database
|
|
||||||
feat(luz): get roster (online and offline)
|
|
||||||
feat(luz): set roster
|
|
||||||
feat(luz): reconnect supervisorcommand
|
|
||||||
feat: thiserror everywhere
|
|
||||||
feat(luz): proper stanza ids
|
|
||||||
test: proper tests
|
|
||||||
ci: doc generation
|
|
||||||
docs(jid): jid
|
|
||||||
feat(peanuts): derive macros for IntoElement and FromElement
|
|
||||||
docs(jabber): connection, starttls, sasl, binding, bound_stream, etc.
|
|
||||||
feat: proper logging for everything basically
|
|
||||||
feat(luz): passwordprovider trait, to avoid storing password in struct
|
|
||||||
feat(luz): auto-reconnect state stored in luz actor, for if e.g. server shut down
|
|
||||||
refactor(luz): dealing properly with all the joinsets and joinhandles
|
|
||||||
feat(peanuts): some kind of way to configure the reader and writer to log the raw xml written to the stream, probably by having a method that allows you to add a log writer to them. will need to investigate some kind of log namespacing.
|
|
||||||
feat(jabber): storing resource within the bound_stream connection
|
|
||||||
|
|
||||||
## done
|
|
||||||
|
|
||||||
feature: starttls
|
feature: starttls
|
||||||
feature: sasl
|
feature: sasl
|
||||||
feature: resource binding
|
feature: resource binding
|
||||||
|
|
||||||
|
## in progress
|
||||||
|
|
||||||
|
feature: logging
|
||||||
|
|
||||||
|
## done
|
||||||
|
|
||||||
feature: jabber client connection
|
feature: jabber client connection
|
||||||
feature: jid
|
feature: jid
|
||||||
|
|
|
@ -15,4 +15,3 @@ tokio-stream = "0.1.17"
|
||||||
tokio-util = "0.7.13"
|
tokio-util = "0.7.13"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
uuid = "1.13.1"
|
|
||||||
|
|
|
@ -1,53 +1,5 @@
|
||||||
PRAGMA foreign_keys = on;
|
|
||||||
|
|
||||||
-- a user jid will never change, only a chat user will change
|
|
||||||
-- TODO: avatar, nick, etc.
|
|
||||||
create table user(
|
|
||||||
jid jid primary key,
|
|
||||||
cached_status text,
|
|
||||||
);
|
|
||||||
|
|
||||||
-- enum for subscription state
|
|
||||||
create table subscription(
|
|
||||||
state text primary key,
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into subscription ( state ) values ('none'), ('pending-out'), ('pending-in'), ('only-out'), ('only-in'), ('out-pending-in'), ('in-pending-out'), ('buddy');
|
|
||||||
|
|
||||||
-- a roster contains users, with client-set nickname
|
|
||||||
CREATE TABLE roster(
|
CREATE TABLE roster(
|
||||||
jid jid primary key,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT,
|
jid TEXT NOT NULL,
|
||||||
subscription text not null,
|
nickname TEXT,
|
||||||
foreign key(subscription) references subscription(state),
|
|
||||||
foreign key(jid) references users(jid)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table groups(
|
|
||||||
group text primary key
|
|
||||||
);
|
|
||||||
|
|
||||||
create table groups_roster(
|
|
||||||
group_id text,
|
|
||||||
contact_id jid,
|
|
||||||
foreign key(group_id) references group(id),
|
|
||||||
foreign key(contact_id) references roster(id),
|
|
||||||
primary key(group_id, contact_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- chat includes reference to user jid chat is with
|
|
||||||
create table chats (
|
|
||||||
id uuid primary key,
|
|
||||||
contact_id jid not null unique,
|
|
||||||
);
|
|
||||||
|
|
||||||
-- messages include reference to chat they are in, and who sent them.
|
|
||||||
create table messages (
|
|
||||||
id uuid primary key,
|
|
||||||
body text,
|
|
||||||
chat_id uuid not null,
|
|
||||||
from jid not null,
|
|
||||||
-- TODO: read bool not null,
|
|
||||||
foreign key(chat_id) references chats(id),
|
|
||||||
foreign key(from) references users(jid)
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::roster::Contact;
|
|
||||||
|
|
||||||
pub enum Chat {
|
|
||||||
Direct(DM),
|
|
||||||
Channel(Channel),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Message {
|
|
||||||
id: Uuid,
|
|
||||||
// contains full contact information
|
|
||||||
from: Contact,
|
|
||||||
// TODO: rich text, other contents, threads
|
|
||||||
body: Body,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Body {
|
|
||||||
body: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DM {
|
|
||||||
contact: Contact,
|
|
||||||
message_history: Vec<Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: group chats
|
|
||||||
pub struct Channel {}
|
|
|
@ -46,21 +46,7 @@ pub enum SupervisorCommand {
|
||||||
Disconnect,
|
Disconnect,
|
||||||
// for if there was a stream error, require to reconnect
|
// for if there was a stream error, require to reconnect
|
||||||
// couldn't stream errors just cause a crash? lol
|
// couldn't stream errors just cause a crash? lol
|
||||||
Reconnect(State),
|
Reconnect,
|
||||||
}
|
|
||||||
|
|
||||||
pub enum State {
|
|
||||||
Write(mpsc::Receiver<WriteMessage>),
|
|
||||||
Read(
|
|
||||||
(
|
|
||||||
SqlitePool,
|
|
||||||
mpsc::Sender<UpdateMessage>,
|
|
||||||
tokio::task::JoinSet<()>,
|
|
||||||
mpsc::Sender<SupervisorCommand>,
|
|
||||||
WriteHandle,
|
|
||||||
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Error>>>>>,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Supervisor {
|
impl Supervisor {
|
||||||
|
@ -115,70 +101,10 @@ impl Supervisor {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
SupervisorCommand::Reconnect(state) => {
|
SupervisorCommand::Reconnect => {
|
||||||
// TODO: please omfg
|
// TODO: please omfg
|
||||||
// send abort to read stream, as already done, consider
|
// send abort to read stream, as already done, consider
|
||||||
let (read_state, mut write_state);
|
todo!()
|
||||||
match state {
|
|
||||||
// TODO: proper state things for read and write thread
|
|
||||||
State::Write(receiver) => {
|
|
||||||
write_state = receiver;
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
let _ = self.reader_handle.send(ReadControl::Abort(send)).await;
|
|
||||||
if let Ok(state) = recv.await {
|
|
||||||
read_state = state;
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State::Read(read) => {
|
|
||||||
read_state = read;
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
let _ = self.writer_handle.send(WriteControl::Abort(send)).await;
|
|
||||||
// TODO: need a tokio select, in case the state arrives from somewhere else
|
|
||||||
if let Ok(state) = recv.await {
|
|
||||||
write_state = state;
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut jid = self.jid.lock().await;
|
|
||||||
let mut domain = jid.domainpart.clone();
|
|
||||||
let connection = jabber::connect_and_login(&mut jid, &*self.password, &mut domain).await;
|
|
||||||
match connection {
|
|
||||||
Ok(c) => {
|
|
||||||
|
|
||||||
let (read, write) = c.split();
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
self.writer_crash = recv;
|
|
||||||
self.writer_handle =
|
|
||||||
WriteControlHandle::reconnect(write, send, write_state);
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
self.reader_crash = recv;
|
|
||||||
let (db, update_sender, tasks, supervisor_command, write_sender, pending_iqs) = read_state;
|
|
||||||
self.reader_handle = ReadControlHandle::reconnect(
|
|
||||||
read,
|
|
||||||
send,
|
|
||||||
db,
|
|
||||||
update_sender,
|
|
||||||
supervisor_command,
|
|
||||||
write_sender,
|
|
||||||
tasks,
|
|
||||||
pending_iqs,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// if reconnection failure, respond to all current write messages with lost connection error. the received processes should complete themselves.
|
|
||||||
write_state.close();
|
|
||||||
while let Some(msg) = write_state.recv().await {
|
|
||||||
let _ = msg.respond_to.send(Err(Error::LostConnection));
|
|
||||||
}
|
|
||||||
let _ = self.sender.send(UpdateMessage::Error(e.into())).await;
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,7 @@ use super::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Read {
|
pub struct Read {
|
||||||
|
// TODO: place iq hashmap here
|
||||||
control_receiver: mpsc::Receiver<ReadControl>,
|
control_receiver: mpsc::Receiver<ReadControl>,
|
||||||
stream: BoundJabberReader<Tls>,
|
stream: BoundJabberReader<Tls>,
|
||||||
on_crash: oneshot::Sender<(
|
on_crash: oneshot::Sender<(
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
AlreadyConnected,
|
AlreadyConnected,
|
||||||
Presence(Reason),
|
|
||||||
Roster(Reason),
|
|
||||||
SendMessage(Reason),
|
|
||||||
AlreadyDisconnected,
|
|
||||||
LostConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Reason {
|
|
||||||
// TODO: organisastion of error into internal error thing
|
|
||||||
Timeout,
|
|
||||||
Stream(stanza::stream_error::Error),
|
|
||||||
Stanza(stanza::stanza_error::Error),
|
|
||||||
Jabber(jabber::Error),
|
Jabber(jabber::Error),
|
||||||
XML(peanuts::Error),
|
XML(peanuts::Error),
|
||||||
SQL(sqlx::Error),
|
SQL(sqlx::Error),
|
||||||
// JID(jid::ParseError),
|
JID(jid::ParseError),
|
||||||
|
AlreadyDisconnected,
|
||||||
LostConnection,
|
LostConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +15,11 @@ impl From<peanuts::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl From<jid::ParseError> for Error {
|
impl From<jid::ParseError> for Error {
|
||||||
// fn from(e: jid::ParseError) -> Self {
|
fn from(e: jid::ParseError) -> Self {
|
||||||
// Self::JID(e)
|
Self::JID(e)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
impl From<sqlx::Error> for Error {
|
impl From<sqlx::Error> for Error {
|
||||||
fn from(e: sqlx::Error) -> Self {
|
fn from(e: sqlx::Error) -> Self {
|
||||||
|
|
|
@ -4,31 +4,27 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chat::{Body, Message};
|
|
||||||
use connection::{write::WriteMessage, SupervisorSender};
|
use connection::{write::WriteMessage, SupervisorSender};
|
||||||
use jabber::JID;
|
use jabber::JID;
|
||||||
use presence::{Offline, Online, Presence};
|
|
||||||
use roster::Contact;
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use stanza::client::{
|
use stanza::{
|
||||||
|
client::{
|
||||||
iq::{self, Iq, IqType},
|
iq::{self, Iq, IqType},
|
||||||
Stanza,
|
Stanza,
|
||||||
|
},
|
||||||
|
roster::{self, Query},
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{mpsc, oneshot, Mutex},
|
sync::{mpsc, oneshot, Mutex},
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::connection::write::WriteHandle;
|
use crate::connection::write::WriteHandle;
|
||||||
use crate::connection::{SupervisorCommand, SupervisorHandle};
|
use crate::connection::{SupervisorCommand, SupervisorHandle};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
mod chat;
|
|
||||||
mod connection;
|
mod connection;
|
||||||
mod error;
|
mod error;
|
||||||
mod presence;
|
|
||||||
mod roster;
|
|
||||||
|
|
||||||
pub struct Luz {
|
pub struct Luz {
|
||||||
receiver: mpsc::Receiver<CommandMessage>,
|
receiver: mpsc::Receiver<CommandMessage>,
|
||||||
|
@ -142,6 +138,7 @@ impl Luz {
|
||||||
self.jid.clone(),
|
self.jid.clone(),
|
||||||
self.db.clone(),
|
self.db.clone(),
|
||||||
self.sender.clone(),
|
self.sender.clone(),
|
||||||
|
// TODO: iq hashmap
|
||||||
self.pending_iqs.clone()
|
self.pending_iqs.clone()
|
||||||
)),
|
)),
|
||||||
None => self.tasks.spawn(msg.handle_offline(
|
None => self.tasks.spawn(msg.handle_offline(
|
||||||
|
@ -194,7 +191,7 @@ impl CommandMessage {
|
||||||
to: None,
|
to: None,
|
||||||
r#type: IqType::Get,
|
r#type: IqType::Get,
|
||||||
lang: None,
|
lang: None,
|
||||||
query: Some(iq::Query::Roster(stanza::roster::Query {
|
query: Some(iq::Query::Roster(roster::Query {
|
||||||
ver: None,
|
ver: None,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
})),
|
})),
|
||||||
|
@ -269,62 +266,16 @@ impl LuzHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CommandMessage {
|
pub enum CommandMessage {
|
||||||
/// connect to XMPP chat server. gets roster and
|
|
||||||
Connect,
|
Connect,
|
||||||
/// disconnect from XMPP chat server.
|
|
||||||
Disconnect,
|
Disconnect,
|
||||||
/// get the roster. if offline, retreive cached version from database. should be stored in application memory
|
/// gets the roster. if offline, retreives cached version from database. should be stored in application memory.
|
||||||
GetRoster,
|
GetRoster,
|
||||||
// add a contact to your roster, with a status of none, no subscriptions
|
SendMessage(JID, String),
|
||||||
AddContact(JID),
|
|
||||||
/// 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),
|
|
||||||
/// send a subscription request, without pre-approval. if not already added to roster server adds to roster.
|
|
||||||
SubscriptionRequest(JID),
|
|
||||||
/// 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),
|
|
||||||
/// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster.
|
|
||||||
AcceptSubscriptionRequest(JID),
|
|
||||||
/// unsubscribe to a contact, but don't remove their subscription.
|
|
||||||
UnsubscribeFromContact(JID),
|
|
||||||
/// stop a contact from being subscribed, but stay subscribed to the contact.
|
|
||||||
UnsubscribeContact(JID),
|
|
||||||
/// remove subscriptions to and from contact, but keep in roster.
|
|
||||||
UnfriendContact(JID),
|
|
||||||
/// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster.
|
|
||||||
DeleteContact(JID),
|
|
||||||
/// set online status
|
|
||||||
SendStatus(Online),
|
|
||||||
SendOffline(Offline),
|
|
||||||
/// send a directed presence (usually to a non-contact).
|
|
||||||
// TODO: should probably make it so people can add non-contact auto presence sharing in the client.
|
|
||||||
SendDirectedPresence {
|
|
||||||
to: JID,
|
|
||||||
presence: Presence,
|
|
||||||
},
|
|
||||||
SendMessage {
|
|
||||||
id: Uuid,
|
|
||||||
to: JID,
|
|
||||||
body: Body,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UpdateMessage {
|
pub enum UpdateMessage {
|
||||||
Error(Error),
|
Error(Error),
|
||||||
Connected(Online),
|
Connected,
|
||||||
Disconnected(Offline),
|
Roster(Vec<roster::Item>),
|
||||||
/// full roster (replace full app roster state with this)
|
|
||||||
Roster(Vec<Contact>),
|
|
||||||
/// roster update (only update app roster state)
|
|
||||||
RosterPush(Contact),
|
|
||||||
Presence {
|
|
||||||
from: JID,
|
|
||||||
presence: Presence,
|
|
||||||
},
|
|
||||||
MessageDispatched(Uuid),
|
|
||||||
Message {
|
|
||||||
from: JID,
|
|
||||||
message: Message,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
use stanza::client::presence::Show;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Online {
|
|
||||||
show: Option<Show>,
|
|
||||||
status: Option<String>,
|
|
||||||
priority: Option<i8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Offline {
|
|
||||||
status: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Presence {
|
|
||||||
Online(Online),
|
|
||||||
Offline(Offline),
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use jid::JID;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Contact {
|
|
||||||
// jid is the id used to reference everything, but not the primary key
|
|
||||||
jid: JID,
|
|
||||||
subscription: Subscription,
|
|
||||||
/// client user defined name
|
|
||||||
name: Option<String>,
|
|
||||||
// TODO: avatar, nickname
|
|
||||||
/// nickname picked by contact
|
|
||||||
// nickname: Option<String>,
|
|
||||||
groups: HashSet<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Subscription {
|
|
||||||
None,
|
|
||||||
PendingOut,
|
|
||||||
PendingIn,
|
|
||||||
OnlyOut,
|
|
||||||
OnlyIn,
|
|
||||||
OutPendingIn,
|
|
||||||
InPendingOut,
|
|
||||||
Buddy,
|
|
||||||
}
|
|
Loading…
Reference in New Issue