Commit 462a43b8 authored by JC Brand's avatar JC Brand

Add new config option `muc_nickname_from_jid`

which if set to `true` will let converse.js automatically take the node part of
a user's JID as their nickname when entering a room.

If there is a nickname clash, then the nickname will be disambiguated by adding
integers to it.

For example, john will become john-1, then john-2 and so forth.
parent 9b130f92
# Changelog # Changelog
## 1.0.6 (Unreleased)
- #674 Polish translation updated to the current master. [ser]
- New config option [muc_nickname_from_jid](https://conversejs.org/docs/html/configuration.html#muc_nickname_from_jid) [jcbrand]
## 1.0.5 (2016-07-28) ## 1.0.5 (2016-07-28)
- In case of nickname conflict when joining a room, allow the user to choose a new one. - In case of nickname conflict when joining a room, allow the user to choose a new one.
[jcbrand] [jcbrand]
......
...@@ -639,6 +639,21 @@ different approach. ...@@ -639,6 +639,21 @@ different approach.
If you're using MAM for archiving chat room messages, you might want to set If you're using MAM for archiving chat room messages, you might want to set
this option to zero. this option to zero.
muc_nickname_from_jid
---------------------
* Default: ``false``
When set to ``true``, then users will not be prompted to provide nicknames for
chat rooms. Instead, the node part of a user's JID (i.e. JID = node@domain/resource)
will be used. If the user's nickname is already taken by another user in the
chat room, then an integer will be added to make it unique.
So, for example, if john@example.com joins a chatroom, his nickname will
automatically be "john". If now john@differentdomain.com tries to join the
room, his nickname will be "john-2", and if john@somethingelse.com joins, then
his nickname will be "john-3", and so forth.
notify_all_room_messages notify_all_room_messages
------------------------ ------------------------
......
...@@ -630,7 +630,7 @@ ...@@ -630,7 +630,7 @@
.c('status').attrs({code:'307'}).nodeTree; .c('status').attrs({code:'307'}).nodeTree;
var view = this.chatboxviews.get('lounge@localhost'); var view = this.chatboxviews.get('lounge@localhost');
view.onChatRoomPresence(presence, {nick: 'dummy', name: 'lounge@localhost'}); view.onChatRoomPresence(presence);
expect(view.$('.chat-area').is(':visible')).toBeFalsy(); expect(view.$('.chat-area').is(':visible')).toBeFalsy();
expect(view.$('.occupants').is(':visible')).toBeFalsy(); expect(view.$('.occupants').is(':visible')).toBeFalsy();
var $chat_body = view.$('.chatroom-body'); var $chat_body = view.$('.chatroom-body');
...@@ -796,7 +796,7 @@ ...@@ -796,7 +796,7 @@
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = this.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'renderPasswordForm').andCallThrough(); spyOn(view, 'renderPasswordForm').andCallThrough();
runs(function () { runs(function () {
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
}); });
waits(250); waits(250);
runs(function () { runs(function () {
...@@ -816,11 +816,11 @@ ...@@ -816,11 +816,11 @@
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'auth'}) .c('error').attrs({by:'lounge@localhost', type:'auth'})
.c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room'); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room');
}.bind(converse)); });
it("will show an error message if the user has been banned", function () { it("will show an error message if the user has been banned", function () {
var presence = $pres().attrs({ var presence = $pres().attrs({
...@@ -831,26 +831,79 @@ ...@@ -831,26 +831,79 @@
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'auth'}) .c('error').attrs({by:'lounge@localhost', type:'auth'})
.c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe('You have been banned from this room'); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You have been banned from this room');
}.bind(converse)); });
it("will show an error message if no nickname was specified for the user", function () { it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false", function () {
var presence = $pres().attrs({ var presence = $pres().attrs({
from:'lounge@localhost/thirdwitch', from:'lounge@localhost/thirdwitch',
id:'n13mt3l', id:'n13mt3l',
to:'dummy@localhost/pda', to:'dummy@localhost/pda',
type:'error'}) type:'error'})
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'modify'}) .c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('jid-malformed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe('No nickname was specified'); expect(view.$el.find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
}.bind(converse)); });
it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true", function () {
/*
<presence
from='coven@chat.shakespeare.lit/thirdwitch'
id='n13mt3l'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error by='coven@chat.shakespeare.lit' type='cancel'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
*/
converse.muc_nickname_from_jid = true;
var attrs = {
from:'lounge@localhost/dummy',
id:'n13mt3l',
to:'dummy@localhost/pda',
type:'error'
};
var presence = $pres().attrs(attrs)
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough();
spyOn(view, 'join').andCallThrough();
// Simulate repeatedly that there's already someone in the room
// with that nickname
view.onChatRoomPresence(presence);
expect(view.join).toHaveBeenCalledWith('dummy-2');
attrs.from = 'lounge@localhost/dummy-2';
presence = $pres().attrs(attrs)
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
view.onChatRoomPresence(presence);
expect(view.join).toHaveBeenCalledWith('dummy-3');
attrs.from = 'lounge@localhost/dummy-3';
presence = $pres().attrs(attrs)
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
view.onChatRoomPresence(presence);
expect(view.join).toHaveBeenCalledWith('dummy-4');
});
it("will show an error message if the user is not allowed to have created the room", function () { it("will show an error message if the user is not allowed to have created the room", function () {
var presence = $pres().attrs({ var presence = $pres().attrs({
...@@ -863,7 +916,7 @@ ...@@ -863,7 +916,7 @@
.c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = this.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms'); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms');
}.bind(converse)); }.bind(converse));
...@@ -876,27 +929,11 @@ ...@@ -876,27 +929,11 @@
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies"); expect(view.$el.find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies");
}.bind(converse)); });
it("will show an error message if the user's nickname is already taken", function () {
var presence = $pres().attrs({
from:'lounge@localhost/thirdwitch',
id:'n13mt3l',
to:'dummy@localhost/pda',
type:'error'})
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'});
expect(view.$el.find('.chatroom-body p:last').text()).toBe(
"The nickname you chose is reserved or currently in use, please choose a different one.");
}.bind(converse));
it("will show an error message if the room doesn't yet exist", function () { it("will show an error message if the room doesn't yet exist", function () {
var presence = $pres().attrs({ var presence = $pres().attrs({
...@@ -907,11 +944,11 @@ ...@@ -907,11 +944,11 @@
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist"); expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist");
}.bind(converse)); });
it("will show an error message if the room has reached its maximum number of occupants", function () { it("will show an error message if the room has reached its maximum number of occupants", function () {
var presence = $pres().attrs({ var presence = $pres().attrs({
...@@ -922,11 +959,11 @@ ...@@ -922,11 +959,11 @@
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
.c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('error').attrs({by:'lounge@localhost', type:'cancel'})
.c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
var view = this.chatboxviews.get('problematic@muc.localhost'); var view = converse.chatboxviews.get('problematic@muc.localhost');
spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'showErrorMessage').andCallThrough();
view.onChatRoomPresence(presence, {'nick': 'dummy'}); view.onChatRoomPresence(presence);
expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants"); expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants");
}.bind(converse)); });
}.bind(converse)); }.bind(converse));
}.bind(converse, mock, test_utils)); }.bind(converse, mock, test_utils));
})); }));
...@@ -159,6 +159,7 @@ ...@@ -159,6 +159,7 @@
this.updateSettings({ this.updateSettings({
allow_muc_invitations: true, allow_muc_invitations: true,
allow_muc: true, allow_muc: true,
muc_nickname_from_jid: false, // Use the node part of the user's JID as room nickname
auto_join_on_invite: false, // Auto-join chatroom on invite auto_join_on_invite: false, // Auto-join chatroom on invite
auto_join_rooms: [], // List of maps {'jid': 'room@example.org', 'nick': 'WizardKing69' }, auto_join_rooms: [], // List of maps {'jid': 'room@example.org', 'nick': 'WizardKing69' },
// providing room jids and nicks or simply a list JIDs. // providing room jids and nicks or simply a list JIDs.
...@@ -694,7 +695,7 @@ ...@@ -694,7 +695,7 @@
'node': 'x-roomuser-item' 'node': 'x-roomuser-item'
}), }),
this.onNickNameFound.bind(this), this.onNickNameFound.bind(this),
this.renderNicknameForm.bind(this) this.onNickNameNotFound.bind(this)
); );
}, },
...@@ -708,13 +709,51 @@ ...@@ -708,13 +709,51 @@
.find('query[node="x-roomuser-item"] identity') .find('query[node="x-roomuser-item"] identity')
.attr('name'); .attr('name');
if (!nick) { if (!nick) {
this.renderNicknameForm(); this.onNickNameNotFound();
} else { } else {
this.join(nick); this.join(nick);
} }
}, },
onNickNameNotFound: function (message) {
if (converse.muc_nickname_from_jid) {
// We try to enter the room with the node part of
// the user's JID.
this.join(Strophe.unescapeNode(Strophe.getNodeFromJid(converse.bare_jid)));
} else {
this.renderNicknameForm(message);
}
},
onNicknameClash: function (presence) {
/* When the nickname is already taken, we either render a
* form for the user to choose a new nickname, or we
* try to make the nickname unique by adding an integer to
* it. So john will become john-2, and then john-3 and so on.
*
* Which option is take depends on the value of
* muc_nickname_from_jid.
*/
if (converse.muc_nickname_from_jid) {
var nick = presence.getAttribute('from').split('/')[1];
if (nick === Strophe.unescapeNode(Strophe.getNodeFromJid(converse.bare_jid))) {
this.join(nick + '-2');
} else {
var del= nick.lastIndexOf("-");
var num = nick.substring(del+1, nick.length);
this.join(nick.substring(0, del+1) + String(Number(num)+1));
}
} else {
this.renderNicknameForm(
__("The nickname you chose is reserved or currently in use, please choose a different one.")
);
}
},
renderNicknameForm: function (message) { renderNicknameForm: function (message) {
/* Render a form which allows the user to choose their
* nickname.
*/
this.$('.chatroom-body').children().addClass('hidden'); this.$('.chatroom-body').children().addClass('hidden');
this.$('span.centered.spinner').remove(); this.$('span.centered.spinner').remove();
if (typeof message !== "string") { if (typeof message !== "string") {
...@@ -883,9 +922,10 @@ ...@@ -883,9 +922,10 @@
return el; return el;
}, },
showErrorMessage: function ($error) { showErrorMessage: function (presence) {
// We didn't enter the room, so we must remove it from the MUC // We didn't enter the room, so we must remove it from the MUC
// add-on // add-on
var $error = $(presence).find('error');
if ($error.attr('type') === 'auth') { if ($error.attr('type') === 'auth') {
if ($error.find('not-authorized').length) { if ($error.find('not-authorized').length) {
this.renderPasswordForm(); this.renderPasswordForm();
...@@ -904,7 +944,7 @@ ...@@ -904,7 +944,7 @@
} else if ($error.find('not-acceptable').length) { } else if ($error.find('not-acceptable').length) {
this.showDisconnectMessage(__("Your nickname doesn't conform to this room's policies")); this.showDisconnectMessage(__("Your nickname doesn't conform to this room's policies"));
} else if ($error.find('conflict').length) { } else if ($error.find('conflict').length) {
this.renderNicknameForm(__("The nickname you chose is reserved or currently in use, please choose a different one.")); this.onNicknameClash(presence);
} else if ($error.find('item-not-found').length) { } else if ($error.find('item-not-found').length) {
this.showDisconnectMessage(__("This room does not (yet) exist")); this.showDisconnectMessage(__("This room does not (yet) exist"));
} else if ($error.find('service-unavailable').length) { } else if ($error.find('service-unavailable').length) {
...@@ -940,7 +980,7 @@ ...@@ -940,7 +980,7 @@
var nick = this.model.get('nick'); var nick = this.model.get('nick');
if ($presence.attr('type') === 'error') { if ($presence.attr('type') === 'error') {
this.model.set('connection_status', Strophe.Status.DISCONNECTED); this.model.set('connection_status', Strophe.Status.DISCONNECTED);
this.showErrorMessage($presence.find('error')); this.showErrorMessage(pres);
} else { } else {
is_self = ($presence.find("status[code='110']").length) || is_self = ($presence.find("status[code='110']").length) ||
($presence.attr('from') === this.model.get('id')+'/'+Strophe.escapeNode(nick)); ($presence.attr('from') === this.model.get('id')+'/'+Strophe.escapeNode(nick));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment