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.
This commit is contained in:
parent
b163d05323
commit
ded9945ed9
@ -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({
|
||||
|
98
spec/muc.js
98
spec/muc.js
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user