mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-26 04:28:00 +01:00
Merge pull request #535 from MF1-MS/mf1-ms/xep_0249_support
Add partial support for XEP-0249 Direct MUC Invitations
This commit is contained in:
commit
50eb94850d
5 changed files with 481 additions and 0 deletions
|
@ -74,6 +74,7 @@ import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
|
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
|
||||||
import org.jivesoftware.smackx.muc.filter.MUCUserStatusCodeFilter;
|
import org.jivesoftware.smackx.muc.filter.MUCUserStatusCodeFilter;
|
||||||
import org.jivesoftware.smackx.muc.packet.Destroy;
|
import org.jivesoftware.smackx.muc.packet.Destroy;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.GroupChatInvitation;
|
||||||
import org.jivesoftware.smackx.muc.packet.MUCAdmin;
|
import org.jivesoftware.smackx.muc.packet.MUCAdmin;
|
||||||
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
|
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
|
||||||
import org.jivesoftware.smackx.muc.packet.MUCItem;
|
import org.jivesoftware.smackx.muc.packet.MUCItem;
|
||||||
|
@ -1063,6 +1064,51 @@ public class MultiUserChat {
|
||||||
connection.sendStanza(message);
|
connection.sendStanza(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invites another user to the room in which one is an occupant. In contrast
|
||||||
|
* to the method "invite", the invitation is sent directly to the user rather
|
||||||
|
* than via the chat room. This is useful when the user being invited is
|
||||||
|
* offline, as otherwise the invitation would be dropped.
|
||||||
|
*
|
||||||
|
* @param address the user to send the invitation to
|
||||||
|
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||||
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
|
*/
|
||||||
|
public void inviteDirectly(EntityBareJid address) throws NotConnectedException, InterruptedException {
|
||||||
|
inviteDirectly(address, null, null, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invites another user to the room in which one is an occupant. In contrast
|
||||||
|
* to the method "invite", the invitation is sent directly to the user rather
|
||||||
|
* than via the chat room. This is useful when the user being invited is
|
||||||
|
* offline, as otherwise the invitation would be dropped.
|
||||||
|
*
|
||||||
|
* @param address the user to send the invitation to
|
||||||
|
* @param reason the purpose for the invitation
|
||||||
|
* @param password specifies a password needed for entry
|
||||||
|
* @param continueAsOneToOneChat specifies if the groupchat room continues a one-to-one chat having the designated thread
|
||||||
|
* @param thread the thread to continue
|
||||||
|
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||||
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
|
*/
|
||||||
|
public void inviteDirectly(EntityBareJid address, String reason, String password, boolean continueAsOneToOneChat, String thread)
|
||||||
|
throws NotConnectedException, InterruptedException {
|
||||||
|
// Add the extension for direct invitation
|
||||||
|
GroupChatInvitation invitationExt = new GroupChatInvitation(room,
|
||||||
|
reason,
|
||||||
|
password,
|
||||||
|
continueAsOneToOneChat,
|
||||||
|
thread);
|
||||||
|
|
||||||
|
Message message = connection.getStanzaFactory().buildMessageStanza()
|
||||||
|
.to(address)
|
||||||
|
.addExtension(invitationExt)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
connection.sendStanza(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener to invitation rejections notifications. The listener will be fired anytime
|
* Adds a listener to invitation rejections notifications. The listener will be fired anytime
|
||||||
* an invitation is declined.
|
* an invitation is declined.
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.filter.AndFilter;
|
import org.jivesoftware.smack.filter.AndFilter;
|
||||||
|
import org.jivesoftware.smack.filter.ExtensionElementFilter;
|
||||||
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
||||||
import org.jivesoftware.smack.filter.NotFilter;
|
import org.jivesoftware.smack.filter.NotFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||||
|
@ -57,6 +58,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||||
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
|
import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
|
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.GroupChatInvitation;
|
||||||
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
|
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
|
||||||
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
||||||
|
|
||||||
|
@ -139,6 +141,11 @@ public final class MultiUserChatManager extends Manager {
|
||||||
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
||||||
new NotFilter(MessageTypeFilter.ERROR));
|
new NotFilter(MessageTypeFilter.ERROR));
|
||||||
|
|
||||||
|
private static final StanzaFilter DIRECT_INVITATION_FILTER =
|
||||||
|
new AndFilter(StanzaTypeFilter.MESSAGE,
|
||||||
|
new ExtensionElementFilter<GroupChatInvitation>(GroupChatInvitation.class),
|
||||||
|
new NotFilter(MessageTypeFilter.ERROR));
|
||||||
|
|
||||||
private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
||||||
100, 1000 * 60 * 60 * 24);
|
100, 1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
|
@ -199,6 +206,33 @@ public final class MultiUserChatManager extends Manager {
|
||||||
};
|
};
|
||||||
connection.addAsyncStanzaListener(invitationPacketListener, INVITATION_FILTER);
|
connection.addAsyncStanzaListener(invitationPacketListener, INVITATION_FILTER);
|
||||||
|
|
||||||
|
// Listens for all messages that include an XEP-0249 GroupChatInvitation extension and fire the invitation
|
||||||
|
// listeners
|
||||||
|
StanzaListener directInvitationStanzaListener = new StanzaListener() {
|
||||||
|
@Override
|
||||||
|
public void processStanza(Stanza stanza) {
|
||||||
|
final Message message = (Message) stanza;
|
||||||
|
GroupChatInvitation invite =
|
||||||
|
stanza.getExtension(GroupChatInvitation.class);
|
||||||
|
|
||||||
|
// Fire event for invitation listeners
|
||||||
|
final MultiUserChat muc = getMultiUserChat(invite.getRoomAddress());
|
||||||
|
final XMPPConnection connection = connection();
|
||||||
|
final EntityJid from = message.getFrom().asEntityJidIfPossible();
|
||||||
|
if (from == null) {
|
||||||
|
LOGGER.warning("Group Chat Invitation from non entity JID in '" + message + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String reason = invite.getReason();
|
||||||
|
final String password = invite.getPassword();
|
||||||
|
final MUCUser.Invite mucInvite = new MUCUser.Invite(reason, from, connection.getUser().asEntityBareJid());
|
||||||
|
for (final InvitationListener listener : invitationsListeners) {
|
||||||
|
listener.invitationReceived(connection, muc, from, reason, password, message, mucInvite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connection.addAsyncStanzaListener(directInvitationStanzaListener, DIRECT_INVITATION_FILTER);
|
||||||
|
|
||||||
connection.addConnectionListener(new ConnectionListener() {
|
connection.addConnectionListener(new ConnectionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void authenticated(XMPPConnection connection, boolean resumed) {
|
public void authenticated(XMPPConnection connection, boolean resumed) {
|
||||||
|
|
|
@ -69,6 +69,10 @@ public class GroupChatInvitation implements ExtensionElement {
|
||||||
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
private final EntityBareJid roomAddress;
|
private final EntityBareJid roomAddress;
|
||||||
|
private final String reason;
|
||||||
|
private final String password;
|
||||||
|
private final String thread;
|
||||||
|
private final boolean continueAsOneToOneChat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new group chat invitation to the specified room address.
|
* Creates a new group chat invitation to the specified room address.
|
||||||
|
@ -79,7 +83,67 @@ public class GroupChatInvitation implements ExtensionElement {
|
||||||
* @param roomAddress the address of the group chat room.
|
* @param roomAddress the address of the group chat room.
|
||||||
*/
|
*/
|
||||||
public GroupChatInvitation(EntityBareJid roomAddress) {
|
public GroupChatInvitation(EntityBareJid roomAddress) {
|
||||||
|
this(roomAddress, null, null, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new group chat invitation to the specified room address.
|
||||||
|
* GroupChat room addresses are in the form <code>room@service</code>,
|
||||||
|
* where <code>service</code> is the name of group chat server, such as
|
||||||
|
* <code>chat.example.com</code>.
|
||||||
|
*
|
||||||
|
* @param roomAddress the address of the group chat room.
|
||||||
|
* @param reason the purpose for the invitation
|
||||||
|
* @param password specifies a password needed for entry
|
||||||
|
* @param continueAsOneToOneChat specifies if the groupchat room continues a one-to-one chat having the designated thread
|
||||||
|
* @param thread the thread to continue
|
||||||
|
*/
|
||||||
|
public GroupChatInvitation(EntityBareJid roomAddress,
|
||||||
|
String reason,
|
||||||
|
String password,
|
||||||
|
boolean continueAsOneToOneChat,
|
||||||
|
String thread) {
|
||||||
this.roomAddress = Objects.requireNonNull(roomAddress);
|
this.roomAddress = Objects.requireNonNull(roomAddress);
|
||||||
|
this.reason = reason;
|
||||||
|
this.password = password;
|
||||||
|
this.continueAsOneToOneChat = continueAsOneToOneChat;
|
||||||
|
this.thread = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the purpose for the invitation.
|
||||||
|
*
|
||||||
|
* @return the address of the group chat room.
|
||||||
|
*/
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the password needed for entry.
|
||||||
|
*
|
||||||
|
* @return the password needed for entry
|
||||||
|
*/
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread to continue.
|
||||||
|
*
|
||||||
|
* @return the thread to continue.
|
||||||
|
*/
|
||||||
|
public String getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the groupchat room continues a one-to-one chat.
|
||||||
|
*
|
||||||
|
* @return whether the groupchat room continues a one-to-one chat.
|
||||||
|
*/
|
||||||
|
public boolean continueAsOneToOneChat() {
|
||||||
|
return continueAsOneToOneChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,6 +171,13 @@ public class GroupChatInvitation implements ExtensionElement {
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
xml.attribute("jid", getRoomAddress());
|
xml.attribute("jid", getRoomAddress());
|
||||||
|
xml.optAttribute("reason", getReason());
|
||||||
|
xml.optAttribute("password", getPassword());
|
||||||
|
xml.optAttribute("thread", getThread());
|
||||||
|
|
||||||
|
if (continueAsOneToOneChat())
|
||||||
|
xml.optBooleanAttribute("continue", true);
|
||||||
|
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2021-2022 Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.muc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.DummyConnection;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||||
|
import org.jivesoftware.smack.test.util.WaitForPacketListener;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.GroupChatInvitation;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.MUCUser.Invite;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
import org.jxmpp.jid.EntityFullJid;
|
||||||
|
import org.jxmpp.jid.EntityJid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test for for following features:
|
||||||
|
* <li>Adds support for Direct MUC invitations (see <a href="https://xmpp.org/extensions/xep-0249.html">XEP-0249</a>), which allows offline users to be invited to group chats.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class MultiUserChatTest extends SmackTestSuite {
|
||||||
|
private static final int RESPONSE_TIMEOUT_IN_MILLIS = 10000;
|
||||||
|
|
||||||
|
private DummyConnection connection;
|
||||||
|
private MultiUserChatManager multiUserChatManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
connection = new DummyConnection();
|
||||||
|
connection.connect();
|
||||||
|
connection.login();
|
||||||
|
|
||||||
|
multiUserChatManager = MultiUserChatManager.getInstanceFor(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInviteDirectly() throws Throwable {
|
||||||
|
EntityBareJid roomJid = JidCreate.entityBareFrom("room@example.com");
|
||||||
|
EntityBareJid userJid = JidCreate.entityBareFrom("user@example.com");
|
||||||
|
|
||||||
|
AtomicBoolean updateRequestSent = new AtomicBoolean();
|
||||||
|
InvokeDirectlyResponder serverSimulator = new InvokeDirectlyResponder() {
|
||||||
|
@Override
|
||||||
|
void verifyRequest(Message updateRequest) {
|
||||||
|
assertEquals(userJid, updateRequest.getTo(), "The provided JID doesn't match the request!");
|
||||||
|
|
||||||
|
GroupChatInvitation groupChatInvitation = (GroupChatInvitation) updateRequest.getExtension(GroupChatInvitation.NAMESPACE);
|
||||||
|
assertNotNull(groupChatInvitation, "Missing GroupChatInvitation extension");
|
||||||
|
assertEquals(roomJid, groupChatInvitation.getRoomAddress());
|
||||||
|
assertNull(groupChatInvitation.getReason());
|
||||||
|
assertNull(groupChatInvitation.getPassword());
|
||||||
|
assertFalse(groupChatInvitation.continueAsOneToOneChat());
|
||||||
|
assertNull(groupChatInvitation.getThread());
|
||||||
|
|
||||||
|
updateRequestSent.set(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
serverSimulator.start();
|
||||||
|
|
||||||
|
// Create multi user chat
|
||||||
|
MultiUserChat multiUserChat = multiUserChatManager.getMultiUserChat(roomJid);
|
||||||
|
|
||||||
|
// Call tested method
|
||||||
|
multiUserChat.inviteDirectly(userJid);
|
||||||
|
|
||||||
|
// Wait for processing requests
|
||||||
|
serverSimulator.join(RESPONSE_TIMEOUT_IN_MILLIS);
|
||||||
|
|
||||||
|
// Check if an error occurred within the simulator
|
||||||
|
final Throwable exception = serverSimulator.getException();
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(updateRequestSent.get(), "Invite directly request not sent");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInviteDirectlyWithAllOptionalAttributes() throws Throwable {
|
||||||
|
EntityBareJid roomJid = JidCreate.entityBareFrom("room@example.com");
|
||||||
|
EntityBareJid userJid = JidCreate.entityBareFrom("user@example.com");
|
||||||
|
String reason = "reason";
|
||||||
|
String password = "password";
|
||||||
|
boolean continueAsOneToOneChat = true;
|
||||||
|
String thread = "e0ffe42b28561960c6b12b944a092794b9683a38";
|
||||||
|
|
||||||
|
AtomicBoolean updateRequestSent = new AtomicBoolean();
|
||||||
|
InvokeDirectlyResponder serverSimulator = new InvokeDirectlyResponder() {
|
||||||
|
@Override
|
||||||
|
void verifyRequest(Message updateRequest) {
|
||||||
|
assertEquals(userJid, updateRequest.getTo(), "The provided JID doesn't match the request!");
|
||||||
|
|
||||||
|
GroupChatInvitation groupChatInvitation = (GroupChatInvitation) updateRequest.getExtension(GroupChatInvitation.NAMESPACE);
|
||||||
|
assertNotNull(groupChatInvitation, "Missing GroupChatInvitation extension");
|
||||||
|
assertEquals(roomJid, groupChatInvitation.getRoomAddress());
|
||||||
|
assertSame(reason, groupChatInvitation.getReason());
|
||||||
|
assertSame(password, groupChatInvitation.getPassword());
|
||||||
|
assertSame(continueAsOneToOneChat, groupChatInvitation.continueAsOneToOneChat());
|
||||||
|
assertSame(thread, groupChatInvitation.getThread());
|
||||||
|
|
||||||
|
updateRequestSent.set(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
serverSimulator.start();
|
||||||
|
|
||||||
|
// Create multi user chat
|
||||||
|
MultiUserChat multiUserChat = multiUserChatManager.getMultiUserChat(roomJid);
|
||||||
|
|
||||||
|
// Call tested method
|
||||||
|
multiUserChat.inviteDirectly(userJid, reason, password, continueAsOneToOneChat, thread);
|
||||||
|
|
||||||
|
// Wait for processing requests
|
||||||
|
serverSimulator.join(RESPONSE_TIMEOUT_IN_MILLIS);
|
||||||
|
|
||||||
|
// Check if an error occurred within the simulator
|
||||||
|
final Throwable exception = serverSimulator.getException();
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(updateRequestSent.get(), "Invite directly request not sent");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReceiveOfflineInvitation() throws XmppStringprepException, Throwable {
|
||||||
|
EntityBareJid roomJid = JidCreate.entityBareFrom("room@example.com");
|
||||||
|
EntityFullJid inviterJid = JidCreate.entityFullFrom("inviter@example.com/user1");
|
||||||
|
EntityBareJid inviteeJid = JidCreate.entityBareFrom("invitee@example.com");
|
||||||
|
Invite invite = new MUCUser.Invite(null, inviterJid);
|
||||||
|
|
||||||
|
GroupChatInvitation groupChatInvitation = new GroupChatInvitation(roomJid);
|
||||||
|
Message sentMessage = connection.getStanzaFactory().buildMessageStanza()
|
||||||
|
.from(inviterJid)
|
||||||
|
.to(inviteeJid)
|
||||||
|
.addExtension(groupChatInvitation)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Prepare listener to receive a group invitation
|
||||||
|
GroupInvitationListener groupInvitationListener = new GroupInvitationListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyInvitation(XMPPConnection conn, MultiUserChat room, EntityJid inviter,
|
||||||
|
String reason, String password, Message message, MUCUser.Invite invitation) {
|
||||||
|
try {
|
||||||
|
// Check all parameters' values.
|
||||||
|
assertSame(connection, conn);
|
||||||
|
assertSame(connection, room.getXmppConnection());
|
||||||
|
assertEquals(roomJid, room.getRoom());
|
||||||
|
assertEquals(inviterJid, inviter);
|
||||||
|
assertNull(reason);
|
||||||
|
assertNull(password);
|
||||||
|
assertSame(sentMessage, message);
|
||||||
|
assertEquals(invite.getReason(), invitation.getReason());
|
||||||
|
assertEquals(invite.getFrom(), invitation.getFrom());
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
this.setError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
multiUserChatManager.addInvitationListener(groupInvitationListener);
|
||||||
|
|
||||||
|
// Simulate sending a message with a group invitation
|
||||||
|
connection.processStanza(sentMessage);
|
||||||
|
|
||||||
|
// Wait for the listener to be called or throw a timeout exception
|
||||||
|
groupInvitationListener.waitUntilInvocationOrTimeout();
|
||||||
|
|
||||||
|
if (groupInvitationListener.getError() != null) {
|
||||||
|
throw groupInvitationListener.getError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReceiveOfflineInvitationWithAllOptionalAttributes() throws XmppStringprepException, Throwable {
|
||||||
|
EntityBareJid roomJid = JidCreate.entityBareFrom("room@example.com");
|
||||||
|
String expectedReason = "reason";
|
||||||
|
String expectedPassword = "password";
|
||||||
|
boolean expectedContinueAsOneToOneChat = true;
|
||||||
|
String expectedThread = "e0ffe42b28561960c6b12b944a092794b9683a38";
|
||||||
|
EntityFullJid inviterJid = JidCreate.entityFullFrom("inviter@example.com/user1");
|
||||||
|
EntityBareJid inviteeJid = JidCreate.entityBareFrom("invitee@example.com");
|
||||||
|
Invite invite = new MUCUser.Invite(expectedReason, inviterJid);
|
||||||
|
|
||||||
|
GroupChatInvitation groupChatInvitation =
|
||||||
|
new GroupChatInvitation(roomJid, expectedReason, expectedPassword, expectedContinueAsOneToOneChat, expectedThread);
|
||||||
|
Message sentMessage = connection.getStanzaFactory().buildMessageStanza()
|
||||||
|
.from(inviterJid)
|
||||||
|
.to(inviteeJid)
|
||||||
|
.addExtension(groupChatInvitation)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Prepare listener to receive a group invitation
|
||||||
|
GroupInvitationListener groupInvitationListener = new GroupInvitationListener() {
|
||||||
|
@Override
|
||||||
|
public void verifyInvitation(XMPPConnection conn, MultiUserChat room, EntityJid inviter,
|
||||||
|
String reason, String password, Message message, MUCUser.Invite invitation) {
|
||||||
|
try {
|
||||||
|
// Check all parameters' values.
|
||||||
|
assertSame(connection, conn);
|
||||||
|
assertSame(connection, room.getXmppConnection());
|
||||||
|
assertEquals(roomJid, room.getRoom());
|
||||||
|
assertEquals(inviterJid, inviter);
|
||||||
|
assertEquals(expectedReason, reason);
|
||||||
|
assertEquals(expectedPassword, password);
|
||||||
|
assertSame(sentMessage, message);
|
||||||
|
assertEquals(invite.getReason(), invitation.getReason());
|
||||||
|
assertEquals(invite.getFrom(), invitation.getFrom());
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
this.setError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
multiUserChatManager.addInvitationListener(groupInvitationListener);
|
||||||
|
|
||||||
|
// Simulate sending a message with a group invitation
|
||||||
|
connection.processStanza(sentMessage);
|
||||||
|
|
||||||
|
// Wait for the listener to be called or throw a timeout exception
|
||||||
|
groupInvitationListener.waitUntilInvocationOrTimeout();
|
||||||
|
|
||||||
|
if (groupInvitationListener.getError() != null) {
|
||||||
|
throw groupInvitationListener.getError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to simulate the server response for invoke directly request.
|
||||||
|
*/
|
||||||
|
private abstract class InvokeDirectlyResponder extends Thread {
|
||||||
|
protected Throwable exception;
|
||||||
|
abstract void verifyRequest(Message updateRequest);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
final Stanza stanza = connection.getSentPacket();
|
||||||
|
if (stanza instanceof Message) {
|
||||||
|
Message message = (Message) stanza;
|
||||||
|
verifyRequest(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exception or error if something went wrong.
|
||||||
|
*
|
||||||
|
* @return the Throwable exception or error that occurred.
|
||||||
|
*/
|
||||||
|
Throwable getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to simulate receiving an invitation.
|
||||||
|
*/
|
||||||
|
private abstract static class GroupInvitationListener extends WaitForPacketListener implements InvitationListener {
|
||||||
|
protected volatile Throwable exception;
|
||||||
|
public abstract void verifyInvitation(XMPPConnection conn, MultiUserChat room, EntityJid inviter,
|
||||||
|
String reason, String password, Message message, MUCUser.Invite invitation);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invitationReceived(XMPPConnection conn, MultiUserChat room, EntityJid inviter,
|
||||||
|
String reason, String password, Message message, MUCUser.Invite invitation) {
|
||||||
|
verifyInvitation(conn, room, inviter, reason, password, message, invitation);
|
||||||
|
reportInvoked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Throwable getError() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setError(Throwable e) {
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -352,6 +352,12 @@
|
||||||
* <td>Efficient roster synchronization.</td>
|
* <td>Efficient roster synchronization.</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
|
* <td>Direct MUC Invitations</td>
|
||||||
|
* <td><a href="https://xmpp.org/extensions/xep-0249.html">XEP-0249</a></td>
|
||||||
|
* <td></td>
|
||||||
|
* <td>Allows sending a MUC invitation directly from the user to the contact with mediation by the room.</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
* <td>Message Carbons</td>
|
* <td>Message Carbons</td>
|
||||||
* <td><a href="https://xmpp.org/extensions/xep-0280.html">XEP-0280</a></td>
|
* <td><a href="https://xmpp.org/extensions/xep-0280.html">XEP-0280</a></td>
|
||||||
* <td>{@link org.jivesoftware.smackx.carbons}</td>
|
* <td>{@link org.jivesoftware.smackx.carbons}</td>
|
||||||
|
|
Loading…
Reference in a new issue