Commit ded9945e authored by JC Brand's avatar JC Brand

MUC: Don't send XEP-0085 CSNs when we don't have voice

Includes some refactoring:

- Don't send an `active` chat state notification when entering a MUC
  I can't think of a good reason why this might be necessary or desired.
- Move `setChatState` form the view to the model
- Remove unused method `handleChatStateNotification`
- Don't store `role` and `affiliation` for the current user on the
  ChatRoom object, but instead on the ChatRoomOccupant object representing
  the user.
parent b163d053
......@@ -821,7 +821,7 @@
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
spyOn(_converse.connection, 'send');
spyOn(view, 'setChatState').and.callThrough();
spyOn(view.model, 'setChatState').and.callThrough();
expect(view.model.get('chat_state')).toBe('active');
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
......@@ -851,7 +851,7 @@
target: view.el.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.setChatState).toHaveBeenCalled();
expect(view.model.setChatState).toHaveBeenCalled();
expect(view.model.get('chat_state')).toBe('composing');
view.onKeyDown({
......
......@@ -54,41 +54,41 @@
test_utils.createContacts(_converse, 'current');
await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300);
await test_utils.openAndEnterChatRoom(_converse, 'chillout@montague.lit', 'romeo');
let jid = 'chillout@montague.lit';
let room = _converse.api.rooms.get(jid);
let muc_jid = 'chillout@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
let room = _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
let chatroomview = _converse.chatboxviews.get(jid);
let chatroomview = _converse.chatboxviews.get(muc_jid);
expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close();
// Test with mixed case
await test_utils.openAndEnterChatRoom(_converse, 'Leisure@montague.lit', 'romeo');
jid = 'Leisure@montague.lit';
room = _converse.api.rooms.get(jid);
muc_jid = 'Leisure@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
room = _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
jid = 'leisure@montague.lit';
room = _converse.api.rooms.get(jid);
muc_jid = 'leisure@montague.lit';
room = _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
jid = 'leiSure@montague.lit';
room = _converse.api.rooms.get(jid);
muc_jid = 'leiSure@montague.lit';
room = _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close();
// Non-existing room
jid = 'chillout2@montague.lit';
room = _converse.api.rooms.get(jid);
muc_jid = 'chillout2@montague.lit';
room = _converse.api.rooms.get(muc_jid);
expect(typeof room === 'undefined').toBeTruthy();
done();
}));
......@@ -331,7 +331,7 @@
* </error>
* </iq>
*/
var result_stanza = $iq({
const result_stanza = $iq({
'type': 'error',
'id': stanza.getAttribute('id'),
'from': view.model.get('jid'),
......@@ -339,10 +339,13 @@
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(test_utils.createRequest(result_stanza));
const input = await test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED);
input.value = 'nicky';
view.el.querySelector('input[type=submit]').click();
expect(view.model.join).toHaveBeenCalled();
await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
......@@ -369,10 +372,11 @@
}).up()
.c('status').attrs({code:'110'}).up()
.c('status').attrs({code:'201'}).nodeTree;
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED);
await test_utils.returnMemberLists(_converse, muc_jid);
// await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
expect(info_texts[0]).toBe('A new groupchat has been created');
......@@ -389,6 +393,7 @@
`<iq id="${iq.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#owner"><x type="submit" xmlns="jabber:x:data"/>`+
`</query></iq>`);
done();
}));
});
......@@ -1302,6 +1307,7 @@
.c('status', {code: '110'});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.saveAffiliationAndRole).toHaveBeenCalled();
debugger;
expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy();
await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button')))
expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy();
......@@ -4719,7 +4725,59 @@
}));
});
describe("A Chat Status Notification", function () {
describe("A XEP-0085 Chat Status Notification", function () {
it("is is not sent out to a MUC if the user is a visitor in a moderated room",
mock.initConverse(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoom.prototype, 'sendChatState').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const features = [
'http://jabber.org/protocol/muc',
'jabber:iq:register',
'muc_passwordprotected',
'muc_hidden',
'muc_temporary',
'muc_membersonly',
'muc_moderated',
'muc_anonymous'
]
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features);
const view = _converse.api.chatviews.get(muc_jid);
view.model.setChatState(_converse.ACTIVE);
expect(view.model.sendChatState).toHaveBeenCalled();
const last_stanza = _converse.connection.sent_stanzas.pop();
expect(Strophe.serialize(last_stanza)).toBe(
`<message to="lounge@montague.lit" type="groupchat" xmlns="jabber:client">`+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
`<no-store xmlns="urn:xmpp:hints"/>`+
`<no-permanent-store xmlns="urn:xmpp:hints"/>`+
`</message>`);
// Romeo loses his voice
const presence = $pres({
to: 'romeo@montague.lit/orchard',
from: `${muc_jid}/some1`
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {'affiliation': 'none', 'role': 'visitor'}).up()
.c('status', {code: '110'});
_converse.connection._dataRecv(test_utils.createRequest(presence));
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
expect(occupant.get('role')).toBe('visitor');
spyOn(_converse.connection, 'send');
view.model.setChatState(_converse.INACTIVE);
expect(view.model.sendChatState.calls.count()).toBe(2);
expect(_converse.connection.send).not.toHaveBeenCalled();
done();
}));
describe("A composing notification", function () {
......
......@@ -877,39 +877,6 @@ converse.plugins.add('converse-chatview', {
}
},
/**
* Mutator for setting the chat state of this chat session.
* Handles clearing of any chat state notification timeouts and
* setting new ones if necessary.
* Timeouts are set when the state being set is COMPOSING or PAUSED.
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
* See XEP-0085 Chat State Notifications.
* @private
* @method _converse.ChatBoxView#setChatState
* @param { string } state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
*/
setChatState (state, options) {
if (!_.isUndefined(this.chat_state_timeout)) {
window.clearTimeout(this.chat_state_timeout);
delete this.chat_state_timeout;
}
if (state === _converse.COMPOSING) {
this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this),
_converse.TIMEOUTS.PAUSED,
_converse.PAUSED
);
} else if (state === _converse.PAUSED) {
this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this),
_converse.TIMEOUTS.INACTIVE,
_converse.INACTIVE
);
}
this.model.set('chat_state', state, options);
return this;
},
async onFormSubmitted (ev) {
ev.preventDefault();
const textarea = this.el.querySelector('.chat-textarea');
......@@ -955,7 +922,7 @@ converse.plugins.add('converse-chatview', {
textarea.focus();
// Suppress events, otherwise superfluous CSN gets set
// immediately after the message, causing rate-limiting issues.
this.setChatState(_converse.ACTIVE, {'silent': true});
this.model.setChatState(_converse.ACTIVE, {'silent': true});
},
updateCharCounter (chars) {
......@@ -1038,7 +1005,7 @@ converse.plugins.add('converse-chatview', {
if (this.model.get('chat_state') !== _converse.COMPOSING) {
// Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message).
this.setChatState(_converse.COMPOSING);
this.model.setChatState(_converse.COMPOSING);
}
},
......@@ -1283,7 +1250,7 @@ converse.plugins.add('converse-chatview', {
if (_converse.connection.connected) {
// Immediately sending the chat state, because the
// model is going to be destroyed afterwards.
this.setChatState(_converse.INACTIVE);
this.model.setChatState(_converse.INACTIVE);
this.model.sendChatState();
}
this.model.close();
......@@ -1336,7 +1303,7 @@ converse.plugins.add('converse-chatview', {
afterShown () {
this.model.clearUnreadMsgCounter();
this.setChatState(_converse.ACTIVE);
this.model.setChatState(_converse.ACTIVE);
this.scrollDown();
if (_converse.auto_focus) {
this.focus();
......@@ -1425,13 +1392,13 @@ converse.plugins.add('converse-chatview', {
onWindowStateChanged (state) {
if (state === 'visible') {
if (!this.model.isHidden()) {
this.setChatState(_converse.ACTIVE);
this.model.setChatState(_converse.ACTIVE);
if (this.model.get('num_unread', 0)) {
this.model.clearUnreadMsgCounter();
}
}
} else if (state === 'hidden') {
this.setChatState(_converse.INACTIVE, {'silent': true});
this.model.setChatState(_converse.INACTIVE, {'silent': true});
this.model.sendChatState();
}
}
......
......@@ -206,7 +206,7 @@ converse.plugins.add('converse-minimize', {
if (!this.model.isScrolledUp()) {
this.model.clearUnreadMsgCounter();
}
this.setChatState(_converse.INACTIVE);
this.model.setChatState(_converse.INACTIVE);
this.show();
/**
* Triggered when a previously minimized chat gets maximized
......@@ -235,7 +235,7 @@ converse.plugins.add('converse-minimize', {
} else {
this.model.set({'scroll': this.content.scrollTop});
}
this.setChatState(_converse.INACTIVE);
this.model.setChatState(_converse.INACTIVE);
this.hide();
/**
* Triggered when a previously maximized chat gets Minimized
......
......@@ -532,7 +532,7 @@ converse.plugins.add('converse-muc-views', {
renderBottomPanel () {
const container = this.el.querySelector('.bottom-panel');
if (this.model.features.get('moderated') && this.model.get('role') === 'visitor') {
if (this.model.features.get('moderated') && this.model.getOwnOccupant().get('role') === 'visitor') {
container.innerHTML = tpl_chatroom_bottom_panel({'__': __});
} else {
if (!container.firstElementChild || !container.querySelector('.sendXMPPMessage')) {
......@@ -708,8 +708,6 @@ converse.plugins.add('converse-muc-views', {
this.showSpinner();
} else if (conn_status === converse.ROOMSTATUS.ENTERED) {
this.hideSpinner();
this.setChatState(_converse.ACTIVE);
this.scrollDown();
if (_converse.auto_focus) {
this.focus();
}
......@@ -725,7 +723,7 @@ converse.plugins.add('converse-muc-views', {
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
{
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
}
);
},
......@@ -789,24 +787,6 @@ converse.plugins.add('converse-muc-views', {
this.insertIntoTextArea(ev.target.textContent);
},
handleChatStateNotification (message) {
/* Override the method on the ChatBoxView base class to
* ignore <gone/> notifications in groupchats.
*
* As laid out in the business rules in XEP-0085
* https://xmpp.org/extensions/xep-0085.html#bizrules-groupchat
*/
if (message.get('fullname') === this.model.get('nick')) {
// Don't know about other servers, but OpenFire sends
// back to you your own chat state notifications.
// We ignore them here...
return;
}
if (message.get('chat_state') !== _converse.GONE) {
_converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments);
}
},
verifyRoles (roles, occupant, show_error=true) {
if (!Array.isArray(roles)) {
throw new TypeError('roles must be an Array');
......@@ -1462,7 +1442,8 @@ converse.plugins.add('converse-muc-views', {
this.renderNicknameForm();
} else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
this.renderPasswordForm();
} else {
} else if (this.model.get('connection_status') == converse.ROOMSTATUS.ENTERED) {
this.hideChatRoomContents();
u.showElement(this.el.querySelector('.chat-area'));
u.showElement(this.el.querySelector('.occupants'));
this.scrollDown();
......@@ -1726,10 +1707,13 @@ converse.plugins.add('converse-muc-views', {
async initialize () {
OrderedListView.prototype.initialize.apply(this, arguments);
this.model.on(
'change:affiliation',
o => (o.get('jid') === _converse.bare_jid) && this.renderInviteWidget()
);
this.chatroomview = this.model.chatroomview;
this.chatroomview.model.features.on('change', this.renderRoomFeatures, this);
this.chatroomview.model.features.on('change:open', this.renderInviteWidget, this);
this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this);
this.chatroomview.model.on('change:hidden_occupants', this.setVisibility, this);
this.render();
await this.model.fetched;
......@@ -1843,7 +1827,7 @@ converse.plugins.add('converse-muc-views', {
shouldInviteWidgetBeShown () {
return _converse.allow_muc_invitations &&
(this.chatroomview.model.features.get('open') ||
this.chatroomview.model.get('affiliation') === "owner"
this.chatroomview.model.getOwnOccupant().get('affiliation') === "owner"
);
},
......
......@@ -401,6 +401,39 @@ converse.plugins.add('converse-chatboxes', {
}
},
/**
* Mutator for setting the chat state of this chat session.
* Handles clearing of any chat state notification timeouts and
* setting new ones if necessary.
* Timeouts are set when the state being set is COMPOSING or PAUSED.
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
* See XEP-0085 Chat State Notifications.
* @private
* @method _converse.ChatBox#setChatState
* @param { string } state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
*/
setChatState (state, options) {
if (!_.isUndefined(this.chat_state_timeout)) {
window.clearTimeout(this.chat_state_timeout);
delete this.chat_state_timeout;
}
if (state === _converse.COMPOSING) {
this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this),
_converse.TIMEOUTS.PAUSED,
_converse.PAUSED
);
} else if (state === _converse.PAUSED) {
this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this),
_converse.TIMEOUTS.INACTIVE,
_converse.INACTIVE
);
}
this.set('chat_state', state, options);
return this;
},
/**
* @private
* @method _converse.ChatBox#shouldShowErrorMessage
......@@ -675,10 +708,8 @@ converse.plugins.add('converse-chatboxes', {
*
* @method _converse.ChatBox#sendMessage
* @memberOf _converse.ChatBox
*
* @param {String} text - The chat message text
* @param {String} spoiler_hint - An optional hint, if the message being sent is a spoiler
*
* @example
* const chat = _converse.api.chats.get('buddy1@example.com');
* chat.sendMessage('hello world');
......@@ -705,11 +736,13 @@ converse.plugins.add('converse-chatboxes', {
return true;
},
/**
* Sends a message with the current XEP-0085 chat state of the user
* as taken from the `chat_state` attribute of the {@link _converse.ChatBox}.
* @private
* @method _converse.ChatBox#sendChatState
*/
sendChatState () {
/* Sends a message with the status of the user in this chat session
* as taken from the 'chat_state' attribute of the chat box.
* See XEP-0085 Chat State Notifications.
*/
if (_converse.send_chat_state_notifications && this.get('chat_state')) {
_converse.api.send(
$msg({
......
......@@ -735,7 +735,7 @@ _converse.initialize = async function (settings, callback) {
this.generateResource = () => `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
/**
* Send out a Chat Status Notification (XEP-0352)
* Send out a Client State Indication (XEP-0352)
* @private
* @method sendCSI
* @memberOf _converse
......@@ -1223,7 +1223,7 @@ _converse.initialize = async function (settings, callback) {
if (credentials) {
this.autoLogin(credentials);
} else if (this.auto_login) {
if (this.credentials_url && _converse.authentication === 'login') {
if (this.credentials_url && _converse.authentication === _converse.LOGIN) {
const data = await getLoginCredentials();
this.autoLogin(data);
} else if (!this.jid) {
......
......@@ -354,7 +354,6 @@ converse.plugins.add('converse-muc', {
// generally unread messages (which *includes* mentions!).
'num_unread_general': 0,
'affiliation': null,
'bookmarked': false,
'chat_state': undefined,
'connection_status': converse.ROOMSTATUS.DISCONNECTED,
......@@ -377,10 +376,10 @@ converse.plugins.add('converse-muc', {
}
this.set('box_id', `box-${btoa(this.get('jid'))}`);
this.initFeatures(); // sendChatState depends on this.features
this.on('change:chat_state', this.sendChatState, this);
this.on('change:connection_status', this.onConnectionStatusChanged, this);
this.initFeatures();
this.initOccupants();
this.registerHandlers();
this.initMessages();
......@@ -707,12 +706,16 @@ converse.plugins.add('converse-muc', {
},
/**
* Sends a message with the status of the user in this chat session
* as taken from the 'chat_state' attribute of the chat box.
* See XEP-0085 Chat State Notifications.
* Sends a message with the current XEP-0085 chat state of the user
* as taken from the `chat_state` attribute of the {@link _converse.ChatRoom}.
* @private
* @method _converse.ChatRoom#sendChatState
*/
sendChatState () {
if (!_converse.send_chat_state_notifications || this.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
if (!_converse.send_chat_state_notifications ||
!this.get('chat_state') ||
this.get('connection_status') !== converse.ROOMSTATUS.ENTERED ||
this.features.get('moderated') && this.getOwnOccupant().get('role') === 'visitor') {
return;
}
const chat_state = this.get('chat_state');
......@@ -970,8 +973,30 @@ converse.plugins.add('converse-muc', {
return _converse.api.sendIQ(iq).then(callback).catch(errback);
},
/**
* Parse the presence stanza for the current user's affiliation.
* Get the {@link _converse.ChatRoomOccupant} instance which
* represents the current user.
* @private
* @method _converse.ChatRoom#getOwnOccupant
* @returns { _converse.ChatRoomOccupant }
*/
getOwnOccupant () {
const occupant = this.occupants.findWhere({'jid': _converse.bare_jid});
if (occupant) {
return occupant;
}
const attributes = {
'jid': _converse.bare_jid,
'resource': Strophe.getResourceFromJid(_converse.resource)
};
return this.occupants.create(attributes);
},
/**
* Parse the presence stanza for the current user's affiliation and
* role and save them on the relevant {@link _converse.ChatRoomOccupant}
* instance.
* @private
* @method _converse.ChatRoom#saveAffiliationAndRole
* @param { XMLElement } pres - A <presence> stanza.
......@@ -982,11 +1007,15 @@ converse.plugins.add('converse-muc', {
if (is_self && !_.isNil(item)) {
const affiliation = item.getAttribute('affiliation');
const role = item.getAttribute('role');
const changes = {};
if (affiliation) {
this.save({'affiliation': affiliation});
changes['affiliation'] = affiliation;
}
if (role) {
this.save({'role': role});
changes['role'] = role;
}
if (!_.isEmpty(changes)) {
this.getOwnOccupant().save(changes);
}
}
},
......@@ -1549,7 +1578,6 @@ converse.plugins.add('converse-muc', {
return;
}
const codes = sizzle('status', x).map(s => s.getAttribute('code'));
codes.forEach(code => {
let message;
if (code === '110' || (code === '100' && !is_self)) {
......@@ -1579,7 +1607,6 @@ converse.plugins.add('converse-muc', {
this.save('nick', nick);
message = __(_converse.muc.new_nickname_messages[code], nick);
}
if (message) {
this.messages.create({'type': 'info', message});
}
......@@ -1689,14 +1716,15 @@ converse.plugins.add('converse-muc', {
if (stanza.getAttribute('type') === 'error') {
return this.onErrorPresence(stanza);
}
this.createInfoMessages(stanza);
if (stanza.querySelector("status[code='110']")) {
this.onOwnPresence(stanza);
}
this.createInfoMessages(stanza);
this.updateOccupantsOnPresence(stanza);
if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
this.save('connection_status', converse.ROOMSTATUS.CONNECTED);
if (this.getOwnOccupant().get('role') !== 'none' &&
this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
this.save('connection_status', converse.ROOMSTATUS.CONNECTED);
}
} else {
this.updateOccupantsOnPresence(stanza);
}
},
......@@ -1716,6 +1744,10 @@ converse.plugins.add('converse-muc', {
* @param { XMLElement } pres - The stanza
*/
onOwnPresence (stanza) {
if (stanza.getAttribute('type') !== 'unavailable') {
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
}
this.updateOccupantsOnPresence(stanza);
this.saveAffiliationAndRole(stanza);
if (stanza.getAttribute('type') === 'unavailable') {
......@@ -1746,13 +1778,12 @@ converse.plugins.add('converse-muc', {
// (in which case Prosody doesn't send a 201 status),
// otherwise the features would have been fetched in
// the "initialize" method already.
if (this.get('affiliation') === 'owner' && this.get('auto_configure')) {
if (this.getOwnOccupant().get('affiliation') === 'owner' && this.get('auto_configure')) {
this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures());
} else {
this.getRoomFeatures();
}
}
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
}
},
......
......@@ -121,17 +121,17 @@
};
utils.getRoomFeatures = async function (_converse, room, server, features=[]) {
const room_jid = `${room}@${server}`.toLowerCase();
const muc_jid = `${room}@${server}`.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
const index = stanzas.length-1;
const stanza = await utils.waitUntil(() => _.filter(
stanzas.slice(index),
iq => iq.querySelector(
`iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).pop());
const features_stanza = $iq({
'from': room_jid,
'from': muc_jid,
'id': stanza.getAttribute('id'),
'to': 'romeo@montague.lit/desktop',
'type': 'result'
......@@ -164,24 +164,18 @@
_converse.connection._dataRecv(utils.createRequest(features_stanza));
};
utils.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
const room = Strophe.getNodeFromJid(muc_jid);
const server = Strophe.getDomainFromJid(muc_jid);
const room_jid = `${room}@${server}`.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
await _converse.api.rooms.open(room_jid);
await utils.getRoomFeatures(_converse, room, server, features);
utils.waitForReservedNick = async function (_converse, muc_jid, nick) {
const view = _converse.chatboxviews.get(muc_jid);
const stanzas = _converse.connection.IQ_stanzas;
const iq = await utils.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${room_jid}"] query[node="x-roomuser-item"]`, s).length
s => sizzle(`iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`, s).length
).pop());
// We remove the stanza, otherwise we might get stale stanzas returned in our filter above.
stanzas.splice(stanzas.indexOf(iq), 1)
// The XMPP server returns the reserved nick for this user.
const view = _converse.chatboxviews.get(room_jid);
const IQ_id = iq.getAttribute('id');
const stanza = $iq({
'type': 'result',
......@@ -191,29 +185,15 @@
}).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));
await utils.waitUntil(() => view.model.get('nick'));
return utils.waitUntil(() => view.model.get('nick'));
};
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
const presence = $pres({
to: _converse.connection.jid,
from: `${room_jid}/${nick}`,
id: u.getUniqueId()
}).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));
await utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
// Now we return the (empty) member lists
utils.returnMemberLists = async function (_converse, muc_jid, members=[]) {
const stanzas = _converse.connection.IQ_stanzas;
const member_IQ = await utils.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
).pop());
const member_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
......@@ -233,7 +213,7 @@
const admin_IQ = await utils.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
).pop());
const admin_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
......@@ -245,7 +225,7 @@
const owner_IQ = await utils.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
).pop());
const owner_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
......@@ -256,6 +236,36 @@
_converse.connection._dataRecv(utils.createRequest(owner_list_stanza));
};
utils.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
muc_jid = muc_jid.toLowerCase();
const room = Strophe.getNodeFromJid(muc_jid);
const server = Strophe.getDomainFromJid(muc_jid);
await _converse.api.rooms.open(muc_jid);
await utils.getRoomFeatures(_converse, room, server, features);
await utils.waitForReservedNick(_converse, muc_jid, nick);
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
const presence = $pres({
to: _converse.connection.jid,
from: `${muc_jid}/${nick}`,
id: u.getUniqueId()
}).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));
const view = _converse.chatboxviews.get(muc_jid);
await utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
await utils.returnMemberLists(_converse, muc_jid, members);
};
utils.clearBrowserStorage = function () {
window.localStorage.clear();
window.sessionStorage.clear();
......
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