Commit acef8fea authored by JC Brand's avatar JC Brand

Refactoring of fetching of reserved nick

- Move `getDefaultNickName` to the model and rename to `getDefaultNick`
- Let `checkForReservedNick` return a promise and save `nick` once received
- Updated `openAndEnterChatRoom` to wait appropriately and remove presence-wrapper
- Update tests to wait appropriately
- Remove presence-wrapper in `getRoomFeatures`
parent 2df9b242
......@@ -68983,31 +68983,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
* If so, we'll use that, otherwise we render the nickname form.
*/
this.showSpinner();
this.model.checkForReservedNick(this.onReservedNicknameFound.bind(this), this.onReservedNicknameNotFound.bind(this));
this.model.checkForReservedNick().then(this.onReservedNickFound.bind(this)).catch(this.onReservedNickNotFound.bind(this));
},
onReservedNicknameFound(iq) {
/* We've received an IQ response from the server which
* might contain the user's reserved nickname.
* If no nickname is found we either render a form for
* them to specify one, or we try to join the groupchat with the
* node of the user's JID.
*
* Parameters:
* (XMLElement) iq: The received IQ stanza
*/
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
nick = identity_el ? identity_el.getAttribute('name') : null;
if (!nick) {
this.onNickNameNotFound();
onReservedNickFound(iq) {
if (this.model.get('nick')) {
this.join();
} else {
this.join(nick);
this.onReservedNickNotFound();
}
},
onReservedNicknameNotFound(message) {
const nick = this.getDefaultNickName();
onReservedNickNotFound(message) {
const nick = this.model.getDefaultNick();
if (nick) {
this.join(nick);
......@@ -69016,21 +69004,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}
},
getDefaultNickName() {
/* The default nickname (used when muc_nickname_from_jid is true)
* is the node part of the user's JID.
* We put this in a separate method so that it can be
* overridden by plugins.
*/
const nick = _converse.xmppstatus.vcard.get('nickname');
if (nick) {
return nick;
} else if (_converse.muc_nickname_from_jid) {
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
}
},
onNicknameClash(presence) {
/* When the nickname is already taken, we either render a
* form for the user to choose a new nickname, or we
......@@ -69043,7 +69016,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
if (_converse.muc_nickname_from_jid) {
const nick = presence.getAttribute('from').split('/')[1];
if (nick === this.getDefaultNickName()) {
if (nick === this.model.getDefaultNick()) {
this.join(nick + '-2');
} else {
const del = nick.lastIndexOf("-");
......@@ -70110,8 +70083,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
},
async onConnectionStatusChanged() {
if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname) {
debugger;
if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname && this.get('reserved_nick')) {
const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'));
if (result.length) {
......@@ -70361,17 +70333,15 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
getRoomFeatures() {
/* Fetch the groupchat disco info, parse it and then save it.
*/
return new Promise((resolve, reject) => {
_converse.api.disco.info(this.get('jid'), null).then(stanza => {
this.parseRoomFeatures(stanza);
resolve();
}).catch(err => {
_converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
_converse.log(err, Strophe.LogLevel.WARN);
reject(err);
});
// XXX: Currently we store disco info on the room itself.
// A better design would probably be to create a
// DiscoEntity for this room, and then to let
// converse-disco manage all disco-related tasks.
// Then we can also use _converse.api.disco.supports.
return _converse.api.disco.info(this.get('jid'), null).then(stanza => this.parseRoomFeatures(stanza)).catch(err => {
_converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
_converse.log(err, Strophe.LogLevel.WARN);
});
},
......@@ -70801,7 +70771,22 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
this.getJidsWithAffiliations(affiliations).then(old_members => this.setAffiliations(deltaFunc(members, old_members))).then(() => this.occupants.fetchMembers()).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
checkForReservedNick(callback, errback) {
getDefaultNick() {
/* The default nickname (used when muc_nickname_from_jid is true)
* is the node part of the user's JID.
* We put this in a separate method so that it can be
* overridden by plugins.
*/
const nick = _converse.xmppstatus.vcard.get('nickname');
if (nick) {
return nick;
} else if (_converse.muc_nickname_from_jid) {
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
}
},
checkForReservedNick() {
/* Use service-discovery to ask the XMPP server whether
* this user has a reserved nickname for this groupchat.
* If so, we'll use that, otherwise we render the nickname form.
......@@ -70810,16 +70795,24 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
* (Function) callback: Callback upon succesful IQ response
* (Function) errback: Callback upon error IQ response
*/
_converse.connection.sendIQ($iq({
return _converse.api.sendIQ($iq({
'to': this.get('jid'),
'from': _converse.connection.jid,
'type': "get"
}).c("query", {
'xmlns': Strophe.NS.DISCO_INFO,
'node': 'x-roomuser-item'
}), callback, errback);
return this;
})).then(iq => {
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
nick = identity_el ? identity_el.getAttribute('name') : null;
this.save({
'reserved_nick': nick,
'nick': nick
}, {
'silent': true
});
return iq;
});
},
async registerNickname() {
......@@ -354,8 +354,8 @@
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
var input = view.el.querySelector('input[name="nick"]');
return test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
}).then(input => {
input.value = 'nicky';
view.el.querySelector('input[type=submit]').click();
expect(view.submitNickname).toHaveBeenCalled();
......@@ -402,7 +402,7 @@
"<query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/>"+
"</query></iq>");
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}));
});
......@@ -1899,7 +1899,8 @@
* </x>
* </presence>
*/
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
.then(() => {
var presence = $pres().attrs({
from:'lounge@localhost/dummy',
to:'dummy@localhost/pda',
......
......@@ -6,39 +6,73 @@
Strophe = converse.env.Strophe,
u = converse.env.utils;
describe("The _converse.api.rooms API", function () {
describe("Chatrooms", function () {
describe("The auto_register_muc_nickname option", function () {
it("allows you to register a user with a room",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
it("allows you to automatically register your nickname when joining a room",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
function (done, _converse) {
let view;
const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo')
.then(() => {
view = _converse.chatboxviews.get(room_jid);
_converse.api.rooms.register(room_jid, _converse.bare_jid, 'romeo');
return test_utils.waitUntil(() => _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector(`iq[to="coven@chat.shakespeare.lit"] query[xmlns="jabber:iq:register"]`)
).pop(), 'nodeTree'));
}).then(stanza => {
expect(stanza.outerHTML)
.toBe(`<iq from="dummy@localhost" to="coven@chat.shakespeare.lit" `+
`type="get" xmlns="jabber:client" id="${stanza.getAttribute('id')}">`+
`<query xmlns="jabber:iq:register"/></iq>`);
// Room does not exist
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'error',
}).c('error', {'type': "cancel"})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"})
_converse.connection._dataRecv(test_utils.createRequest(result));
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}));
let view;
const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo')
.then(() => {
return test_utils.waitUntil(() => _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector(
`iq[to="coven@chat.shakespeare.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)
).pop(), 'nodeTree'));
}).then(stanza => {
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
spyOn(view.model, 'parseRoomFeatures').and.callThrough();
const features_stanza = $iq({
'from': room_jid,
'id': stanza.getAttribute('id'),
'to': 'dummy@localhost/desktop',
'type': 'result'
})
.c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'conference',
'name': 'A Dark Cave',
'type': 'text'
}).up()
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
.c('feature', {'var': 'jabber:iq:register'}).up()
.c('feature', {'var': 'muc_passwordprotected'}).up()
.c('feature', {'var': 'muc_hidden'}).up()
.c('feature', {'var': 'muc_temporary'}).up()
.c('feature', {'var': 'muc_open'}).up()
.c('feature', {'var': 'muc_unmoderated'}).up()
.c('feature', {'var': 'muc_nonanonymous'});
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
return test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300)
}).then(() => {
return test_utils.waitUntil(() => _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector(`iq[to="coven@chat.shakespeare.lit"] query[xmlns="jabber:iq:register"]`)
).pop(), 'nodeTree'));
}).then(stanza => {
expect(stanza.outerHTML)
.toBe(`<iq from="dummy@localhost" to="coven@chat.shakespeare.lit" `+
`type="get" xmlns="jabber:client" id="${stanza.getAttribute('id')}">`+
`<query xmlns="jabber:iq:register"/></iq>`);
// Room does not exist
view = _converse.chatboxviews.get(room_jid);
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'error',
}).c('error', {'type': "cancel"})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"})
_converse.connection._dataRecv(test_utils.createRequest(result));
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}));
});
});
}));
......@@ -262,7 +262,7 @@
type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree()
);
return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator"));
return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator").length);
}).then(() => {
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
const indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
......
......@@ -1203,33 +1203,21 @@
* If so, we'll use that, otherwise we render the nickname form.
*/
this.showSpinner();
this.model.checkForReservedNick(
this.onReservedNicknameFound.bind(this),
this.onReservedNicknameNotFound.bind(this)
)
},
onReservedNicknameFound (iq) {
/* We've received an IQ response from the server which
* might contain the user's reserved nickname.
* If no nickname is found we either render a form for
* them to specify one, or we try to join the groupchat with the
* node of the user's JID.
*
* Parameters:
* (XMLElement) iq: The received IQ stanza
*/
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
nick = identity_el ? identity_el.getAttribute('name') : null;
if (!nick) {
this.onNickNameNotFound();
this.model.checkForReservedNick()
.then(this.onReservedNickFound.bind(this))
.catch(this.onReservedNickNotFound.bind(this));
},
onReservedNickFound (iq) {
if (this.model.get('nick')) {
this.join();
} else {
this.join(nick);
this.onReservedNickNotFound();
}
},
onReservedNicknameNotFound (message) {
const nick = this.getDefaultNickName();
onReservedNickNotFound (message) {
const nick = this.model.getDefaultNick();
if (nick) {
this.join(nick);
} else {
......@@ -1237,20 +1225,6 @@
}
},
getDefaultNickName () {
/* The default nickname (used when muc_nickname_from_jid is true)
* is the node part of the user's JID.
* We put this in a separate method so that it can be
* overridden by plugins.
*/
const nick = _converse.xmppstatus.vcard.get('nickname');
if (nick) {
return nick;
} else if (_converse.muc_nickname_from_jid) {
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
}
},
onNicknameClash (presence) {
/* When the nickname is already taken, we either render a
* form for the user to choose a new nickname, or we
......@@ -1262,7 +1236,7 @@
*/
if (_converse.muc_nickname_from_jid) {
const nick = presence.getAttribute('from').split('/')[1];
if (nick === this.getDefaultNickName()) {
if (nick === this.model.getDefaultNick()) {
this.join(nick + '-2');
} else {
const del= nick.lastIndexOf("-");
......
......@@ -406,17 +406,17 @@
getRoomFeatures () {
/* Fetch the groupchat disco info, parse it and then save it.
*/
return new Promise((resolve, reject) => {
_converse.api.disco.info(this.get('jid'), null)
.then((stanza) => {
this.parseRoomFeatures(stanza);
resolve()
}).catch((err) => {
_converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
_converse.log(err, Strophe.LogLevel.WARN);
reject(err);
});
});
// XXX: Currently we store disco info on the room itself.
// A better design would probably be to create a
// DiscoEntity for this room, and then to let
// converse-disco manage all disco-related tasks.
// Then we can also use _converse.api.disco.supports.
return _converse.api.disco.info(this.get('jid'), null)
.then(stanza => this.parseRoomFeatures(stanza))
.catch(err => {
_converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
_converse.log(err, Strophe.LogLevel.WARN);
});
},
getRoomJIDAndNick (nick) {
......@@ -791,7 +791,21 @@
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
checkForReservedNick (callback, errback) {
getDefaultNick () {
/* The default nickname (used when muc_nickname_from_jid is true)
* is the node part of the user's JID.
* We put this in a separate method so that it can be
* overridden by plugins.
*/
const nick = _converse.xmppstatus.vcard.get('nickname');
if (nick) {
return nick;
} else if (_converse.muc_nickname_from_jid) {
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
}
},
checkForReservedNick () {
/* Use service-discovery to ask the XMPP server whether
* this user has a reserved nickname for this groupchat.
* If so, we'll use that, otherwise we render the nickname form.
......@@ -800,7 +814,7 @@
* (Function) callback: Callback upon succesful IQ response
* (Function) errback: Callback upon error IQ response
*/
_converse.connection.sendIQ(
return _converse.api.sendIQ(
$iq({
'to': this.get('jid'),
'from': _converse.connection.jid,
......@@ -808,9 +822,16 @@
}).c("query", {
'xmlns': Strophe.NS.DISCO_INFO,
'node': 'x-roomuser-item'
}),
callback, errback);
return this;
})
).then(iq => {
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
nick = identity_el ? identity_el.getAttribute('name') : null;
this.save({
'reserved_nick': nick,
'nick': nick
}, {'silent': true});
return iq;
});
},
async registerNickname () {
......
......@@ -126,61 +126,60 @@
};
utils.openAndEnterChatRoom = function (_converse, room, server, nick) {
let last_stanza;
let last_stanza, view;
return new Promise((resolve, reject) => {
return _converse.api.rooms.open(`${room}@${server}`).then(() => {
const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
// We pretend this is a new room, so no disco info is returned.
let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
const IQ_id = last_stanza.getAttribute('id');
const features_stanza = $iq({
'from': room+'@'+server,
'id': IQ_id,
'to': nick+'@'+server,
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(utils.createRequest(features_stanza));
utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas, (node) => {
const query = node.nodeTree.querySelector('query');
if (query && query.getAttribute('node') === 'x-roomuser-item') {
last_stanza = node.nodeTree;
return true;
}
}).length
}).then(function () {
// The XMPP server returns the reserved nick for this user.
const IQ_id = last_stanza.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': view.model.get('jid'),
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
_converse.connection._dataRecv(utils.createRequest(stanza));
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
var presence = $pres({
to: _converse.connection.jid,
from: room+'@'+server+'/'+nick,
id: 'DC352437-C019-40EC-B590-AF29E879AF97'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'owner',
jid: _converse.bare_jid,
role: 'moderator'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence));
resolve();
}).catch(_.partial(console.error, _));
}).catch(_.partial(console.error, _));
return _converse.api.rooms.open(`${room}@${server}`).then(() => {
view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
// We pretend this is a new room, so no disco info is returned.
last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
const IQ_id = last_stanza.getAttribute('id');
const features_stanza = $iq({
'from': room+'@'+server,
'id': IQ_id,
'to': nick+'@'+server,
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(utils.createRequest(features_stanza));
return utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas, (node) => {
const query = node.nodeTree.querySelector('query');
if (query && query.getAttribute('node') === 'x-roomuser-item') {
last_stanza = node.nodeTree;
return true;
}
}).length
});
}).then(() => {
// The XMPP server returns the reserved nick for this user.
const IQ_id = last_stanza.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': view.model.get('jid'),
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
_converse.connection._dataRecv(utils.createRequest(stanza));
return utils.waitUntil(() => view.model.get('nick'));
}).then(() => {
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
var presence = $pres({
to: _converse.connection.jid,
from: room+'@'+server+'/'+nick,
id: 'DC352437-C019-40EC-B590-AF29E879AF97'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'owner',
jid: _converse.bare_jid,
role: 'moderator'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence));
return utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED);
}).catch(_.partial(console.error, _));
};
......
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