Bug 1050779 - Allow and handle receiving invitations to XMPP room chat. r=aleth
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,16 +3,17 @@ codebase which lives in this repository.
here, you may add your name and, optionally, email address in the
appropriate place.
For a full list of the people who are credited with making a
contribution to Mozilla, see http://www.mozilla.org/credits/ .
Aaron Kaluszka <ask@swva.net>
Aaron Leventhal <aaronl@netscape.com>
+Abdelrhman Ahmed <a.ahmed1026@gmail.com>
ActiveState Software Inc
Adam Becevello <abecevello@sympatico.ca>
Adam Christian <adam.christian@gmail.com>
Adam D. Moss <adam@gimp.org>
Adam Lock <adamlock@netscape.com>
Adrian Havill <havill@redhat.com>
Akihiro Misaki <spitfire.kuden@gmail.com>
Akkana Peck <akkana@netscape.com>
--- a/chat/locales/en-US/xmpp.properties
+++ b/chat/locales/en-US/xmpp.properties
@@ -64,16 +64,26 @@ tooltip.subscription=Subscription
# The _ character won't be displayed; it indicates the next
# character of the string should be used as the access key for this
# field.
chatRoomField.room=_Room
chatRoomField.server=_Server
chatRoomField.nick=_Nick
chatRoomField.password=_Password
+# LOCALIZATION NOTE (conversation.muc.*):
+# These are displayed as a system message when a chatroom invitation is
+# received.
+# %1$S is the inviter.
+# %2$S is the room.
+# %3$S is the reason which is a message provided by the person sending the
+# invitation.
+conversation.muc.invitationWithReason=%1$S has invited you to join %2$S: %3$S
+conversation.muc.invitationWithoutReason=%1$S has invited you to join %2$S
+
# LOCALIZATION NOTE (options.*):
# These are the protocol specific options shown in the account manager and
# account wizard windows.
options.resource=Resource
options.priority=Priority
options.connectionSecurity=Connection security
options.connectionSecurity.requireEncryption=Require encryption
options.connectionSecurity.opportunisticTLS=Use encryption if available
--- a/chat/protocols/xmpp/xmpp-xml.jsm
+++ b/chat/protocols/xmpp/xmpp-xml.jsm
@@ -40,16 +40,17 @@ const NS = {
disco_info : "http://jabber.org/protocol/disco#info",
disco_items : "http://jabber.org/protocol/disco#items",
caps : "http://jabber.org/protocol/caps",
//addressing
address : "http://jabber.org/protocol/address",
muc_user : "http://jabber.org/protocol/muc#user",
+ conference : "jabber:x:conference",
muc : "http://jabber.org/protocol/muc",
register : "jabber:iq:register",
delay : "urn:xmpp:delay",
delay_legacy : "jabber:x:delay",
bookmarks : "storage:bookmarks",
chatstates : "http://jabber.org/protocol/chatstates",
event : "jabber:x:event",
stanzas : "urn:ietf:params:xml:ns:xmpp-stanzas",
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -1011,16 +1011,68 @@ const XMPPAccountPrototype = {
// The join was successful.
muc.left = false;
muc.onPresenceStanza(aStanza);
}
else if (jid != this.normalize(this._connection._jid.jid))
this.WARN("received presence stanza for unknown buddy " + from);
},
+ // Returns null if not an invitation stanza, and an object
+ // describing the invitation otherwise.
+ parseInvitation: function(aStanza) {
+ let x = aStanza.getElement(["x"]);
+ if (!x)
+ return null;
+ let retVal = {};
+
+ // XEP-0045. Direct Invitation (7.8.1)
+ // Described in XEP-0249.
+ // jid (chatroom) is required.
+ // Password, reason, continue and thread are optional.
+ if (x.uri == Stanza.NS.conference) {
+ if (!x.attributes["jid"]) {
+ this.WARN("Received an invitation with missing MUC jid.");
+ return null;
+ }
+ retVal.mucJid = this.normalize(x.attributes["jid"]);
+ retVal.from = this.normalize(aStanza.attributes["from"]);
+ retVal.password = x.attributes["password"];
+ retVal.reason = x.attributes["reason"];
+ retVal.continue = x.attributes["continue"];
+ retVal.thread = x.attributes["thread"];
+ return retVal;
+ }
+
+ // XEP-0045. Mediated Invitation (7.8.2)
+ // Sent by the chatroom on behalf of someone in the chatroom.
+ // jid (chatroom) and from (inviter) are required.
+ // password and reason are optional.
+ if (x.uri == Stanza.NS.muc_user) {
+ let invite = x.getElement(["invite"]);
+ if (!invite || !invite.attributes["from"]) {
+ this.WARN("Received an invitation with missing MUC invite or from.");
+ return null;
+ }
+ retVal.mucJid = this.normalize(aStanza.attributes["from"]);
+ retVal.from = this.normalize(invite.attributes["from"]);
+ let continueElement = invite.getElement(["continue"]);
+ retVal.continue = !!continueElement;
+ if (continueElement)
+ retVal.thread = continueElement.attributes["thread"];
+ if (x.getElement(["password"]))
+ retVal.password = x.getElement(["password"]).innerText;
+ if (invite.getElement(["reason"]))
+ retVal.reason = invite.getElement(["reason"]).innerText;
+ return retVal;
+ }
+
+ return null;
+ },
+
/* Called when a message stanza is received */
onMessageStanza: function(aStanza) {
let norm = this.normalize(aStanza.attributes["from"]);
let type = aStanza.attributes["type"];
let body;
let b = aStanza.getElement(["body"]);
if (b) {
@@ -1060,16 +1112,42 @@ const XMPPAccountPrototype = {
// attributes.
if (s)
muc.setTopic(s.innerText);
muc.incomingMessage(body, aStanza, date);
return;
}
+ let invitation = this.parseInvitation(aStanza);
+ if (invitation) {
+ if (invitation.reason) {
+ body = _("conversation.muc.invitationWithReason",
+ invitation.from, invitation.mucJid, invitation.reason);
+ }
+ else {
+ body = _("conversation.muc.invitationWithoutReason",
+ invitation.from, invitation.mucJid);
+ }
+ if (Services.prefs.getIntPref("messenger.conversations.autoAcceptChatInvitations") == 1) {
+ // Auto-accept the invitation.
+ let chatRoomFields = this.getChatRoomDefaultFieldValues(invitation.mucJid);
+ if (invitation.password)
+ chatRoomFields.setValue("password", invitation.password);
+ let muc = this.joinChat(chatRoomFields);
+ muc.writeMessage(muc.name, body, {system: true});
+ return;
+ }
+ // Otherwise, just notify the user.
+ let conv = this.createConversation(invitation.from);
+ if (conv)
+ conv.writeMessage(invitation.from, body, {system: true});
+ return;
+ }
+
let conv = this.createConversation(norm);
if (!conv)
return;
conv.incomingMessage(body, aStanza, date);
}
else if (type == "error") {
let conv = this.createConversation(norm);
if (conv)