Support Multi-Session Nicks joins

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2022-09-19 15:16:26 +02:00
parent 919f3cf754
commit 7a687df552
3 changed files with 171 additions and 37 deletions

View file

@ -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

View file

@ -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::<Vec<MucItem>>();
*/
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::<Vec<MucItem>>();
// 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<BroadcastPresence> = {
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#"<item xmlns="http://jabber.org/protocol/muc#user" affiliation="owner" role="moderator" jid="bar@qxx/foo"/>"#
.parse::<Element>()
.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#"<item xmlns="http://jabber.org/protocol/muc#user" affiliation="owner" role="moderator" jid="bar@qxx/foo"/>"#
.parse::<Element>()
.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()
])
);

View file

@ -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<BareJid, Room> = 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!(),
}
}