diff --git a/README.md b/README.md index d2606b6..51e7f83 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,9 @@ file. ### XMPP -- [ ] Join - * [x] Single session - * [ ] Multiple sessions non-MSN - * [ ] MSN +- [x] Join + * [x] Normal sessions + * [x] MSN - [ ] Presence * [x] Updates * [x] Resync diff --git a/src/room.rs b/src/room.rs index 13ce443..d62f281 100644 --- a/src/room.rs +++ b/src/room.rs @@ -114,28 +114,14 @@ impl Room { _ => false, }; - /* - // MucItems to be sent to sessions of this occupant - let self_items = own_occupant.iter().map(|session| { - MucItem { - affiliation: Affiliation::Owner, - role: Role::Moderator, - jid: Some(session.clone()), - nick: None, - actor: None, - continue_: None, - reason: None, - } - }).collect::>(); - */ - for (_, other) in self.occupants.iter() { + if own_occupant.nick == other.nick { + continue; + } if sync { // Send presences from others to participant. - if own_occupant.nick != &other.nick { - let presence = presence_to_new.clone() - .with_from(Jid::Full(other.participant.clone())); - component.send_stanza(presence).await?; - } + let presence = presence_to_new.clone() + .with_from(Jid::Full(other.participant.clone())); + component.send_stanza(presence).await?; } if update || leave { // Send presence from participant to others. for session in other.iter() { @@ -151,6 +137,39 @@ impl Room { } } + // MucItems to be sent to sessions of this occupant + let self_items = own_occupant.iter().map(|session| { + MucItem { + affiliation: Affiliation::Owner, + role: if leave { Role::None } else { Role::Moderator }, + jid: Some(session.clone()), + nick: None, + actor: None, + continue_: None, + reason: None, + } + }).collect::>(); + + // Multi-Session Nick: For this occupant, include all sessions all with item@jid discovered + // so they can identify each other as being the same account under the same nick. + let session_presence = Presence::new( + if leave { PresenceType::Unavailable } else { PresenceType::None } + ) + .with_from(Jid::Full(own_occupant.participant.clone())); + + for session in own_occupant.iter() { + if session == own_session { + continue; + } + let presence = session_presence.clone() + .with_to(Jid::Full(session.clone())) + .with_payloads(vec![MucUser { + status: vec![], + items: self_items.clone(), + }.into()]); + component.send_stanza(presence).await?; + } + // Send self-presence if sync || leave { // New participant to all other sessions @@ -167,12 +186,11 @@ impl Room { MucStatus::SelfPresence, MucStatus::AssignedNick, ] }, - items: vec![ - MucItem::new( - Affiliation::Owner, - if leave { Role::None } else { Role::Moderator }, - ) - ], + items: if leave { + vec![MucItem::new(Affiliation::Owner, Role::None)] + } else { + self_items + }, } .into()]); component.send_stanza(self_presence).await?; @@ -231,7 +249,9 @@ impl Room { let mode: Option = { if let Some(occupant) = self.occupants.get_mut(&new_nick) { match occupant.add_session(realjid.clone()) { - Ok(_) => None, + Ok(_) => { + Some(BroadcastPresence::Join) + }, Err(Error::SessionAlreadyExists(_)) => { Some(BroadcastPresence::Resync) }, @@ -372,7 +392,7 @@ mod tests { use std::str::FromStr; use crate::component::TestComponent; use xmpp_parsers::{ - BareJid, + BareJid, Element, presence::{Presence, Type as PresenceType}, muc::{ MucUser, @@ -402,6 +422,12 @@ mod tests { let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); room.occupants.insert(participant3.resource.clone(), occupant3.clone()); + let self_item = MucItem::try_from( + r#""# + .parse::() + .unwrap() + ).unwrap(); + // BroadcastPresence::Resync let mut component = TestComponent::new(vec![]); @@ -436,7 +462,7 @@ mod tests { .with_payloads(vec![ MucUser { status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + items: vec![self_item.clone()], }.into() ]) ); @@ -520,6 +546,12 @@ mod tests { let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); room.occupants.insert(participant3.resource.clone(), occupant3.clone()); + let self_item = MucItem::try_from( + r#""# + .parse::() + .unwrap() + ).unwrap(); + // BroadcastPresence::Join let mut component = TestComponent::new(vec![]); @@ -578,7 +610,7 @@ mod tests { .with_payloads(vec![ MucUser { status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + items: vec![self_item], }.into() ]) ); diff --git a/src/tests.rs b/src/tests.rs index f6a8d8e..83151ce 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -101,7 +101,11 @@ async fn test_join_presence_empty_room() { .with_to(Jid::Full(from.clone())) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::Moderator); + item.jid = Some(from.clone()); + item + }], } .into()]), ); @@ -256,7 +260,11 @@ async fn test_join_presence_existing_room() { .with_to(Jid::Full(realjid2.clone())) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::Moderator); + item.jid = Some(realjid2.clone()); + item + }], } .into()]), ); @@ -347,7 +355,11 @@ async fn test_presence_resync() { .with_to(Jid::Full(realjid1.clone())) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::Moderator); + item.jid = Some(realjid1.clone()); + item + }], } .into()]), ); @@ -488,3 +500,94 @@ async fn test_leave_room_not_last() { None => panic!(), } } + +#[tokio::test] +async fn test_join_msn() { + let barejid = BareJid::from_str("foo@bar").unwrap(); + let realjid1 = barejid.clone().with_resource("qxx"); + let realjid2 = barejid.clone().with_resource("hah"); + let roomjid = COMPONENT_JID.clone().with_node("room"); + let participant1 = roomjid.clone().with_resource("nick1"); + + let join1: Element = Presence::new(PresenceType::None) + .with_from(Jid::Full(realjid1.clone())) + .with_to(Jid::Full(participant1.clone())) + .with_payloads(vec![Muc::new().into()]) + .into(); + + let join2: Element = Presence::new(PresenceType::None) + .with_from(Jid::Full(realjid2.clone())) + .with_to(Jid::Full(participant1.clone())) + .with_payloads(vec![Muc::new().into()]) + .into(); + + let mut component = TestComponent::new(vec![join1, join2]); + let mut rooms: HashMap = HashMap::new(); + + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(participant1.clone())) + .with_to(Jid::Full(realjid1.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::Moderator); + item.jid = Some(realjid1.clone()); + item + }], + }.into()]) + ); + + component.expect_message(|_| (), "Subject message for participant1"); + + // New session joins + // Participant1 presence for participant2 + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(participant1.clone())) + .with_to(Jid::Full(realjid1.clone())) + .with_payloads(vec![MucUser { + status: Vec::new(), + items: { + let item = MucItem::new(Affiliation::Owner, Role::Moderator); + let mut item1 = item.clone(); + item1.jid = Some(realjid1.clone()); + let mut item2 = item.clone(); + item2.jid = Some(realjid2.clone()); + vec![item1, item2] + }, + }.into()]) + ); + + // Self-presence for participant2 + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(participant1.clone())) + .with_to(Jid::Full(realjid2.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], + items: { + let item = MucItem::new(Affiliation::Owner, Role::Moderator); + let mut item1 = item.clone(); + item1.jid = Some(realjid1.clone()); + let mut item2 = item.clone(); + item2.jid = Some(realjid2.clone()); + vec![item1, item2] + }, + }.into()]) + ); + + component.expect_message(|_| (), "Subject message for participant2"); + + handle_stanza(&mut component, &mut rooms).await.unwrap(); + component.assert(); + + assert_eq!(rooms.len(), 1); + match rooms.get(&roomjid) { + Some(room) => { + assert_eq!(room.occupants.len(), 1); + assert_eq!(room.occupants.get(&participant1.resource).unwrap().sessions.len(), 2); + }, + None => panic!(), + } +}