From 67bcc00f10ccebdd5f52a25a52c7bc5259d34c71 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 1 Jul 2019 10:44:59 +0200 Subject: [PATCH] Associate `ChatRoomOccupant` to `ChatRoomMessage` and use promises to indicate when an occupant or contact has been set --- .eslintrc.json | 2 +- spec/messages.js | 15 ++-- spec/muc.js | 16 ++-- spec/room_registration.js | 122 ++++++++++------------------- spec/spoilers.js | 5 +- src/converse-chatview.js | 14 ++-- src/converse-message-view.js | 30 +++++-- src/headless/converse-chatboxes.js | 11 ++- src/headless/converse-muc.js | 71 +++++++++++------ src/headless/converse-vcard.js | 8 +- src/templates/message.html | 2 +- tests/utils.js | 39 ++++++++- 12 files changed, 189 insertions(+), 146 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 00925aaab..2942d3c40 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -233,7 +233,7 @@ } ], "prefer-numeric-literals": "error", - "prefer-promise-reject-errors": "error", + "prefer-promise-reject-errors": "off", "prefer-reflect": "off", "prefer-rest-params": "off", "prefer-spread": "off", diff --git a/spec/messages.js b/spec/messages.js index a54c268d5..5f9f70618 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -2374,8 +2374,8 @@ }).c('body').t('I wrote this message!').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - expect(view.model.messages.last().get('affiliation')).toBe('owner'); - expect(view.model.messages.last().get('role')).toBe('moderator'); + expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); + expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author moderator'); @@ -2401,8 +2401,8 @@ }).c('body').t('Another message!').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - expect(view.model.messages.last().get('affiliation')).toBe('member'); - expect(view.model.messages.last().get('role')).toBe('participant'); + expect(view.model.messages.last().occupant.get('affiliation')).toBe('member'); + expect(view.model.messages.last().occupant.get('role')).toBe('participant'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant'); @@ -2422,8 +2422,8 @@ view.model.sendMessage('hello world'); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3); - expect(view.model.messages.last().get('affiliation')).toBe('owner'); - expect(view.model.messages.last().get('role')).toBe('moderator'); + expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); + expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(3); expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author moderator'); done(); @@ -2969,6 +2969,7 @@ async function (done, _converse) { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'tom'); + const view = _converse.api.chatviews.get('lounge@montague.lit'); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( @@ -3023,7 +3024,7 @@ await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === 'hello z3r0 gibson sw0rdf1sh, how are you?', 500); - const correction = _converse.connection.send.calls.all()[1].args[0]; + const correction = _converse.connection.send.calls.all()[2].args[0]; expect(correction.toLocaleString()) .toBe(` view.el.querySelectorAll('form.chatroom-form').length) expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1); expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); - var membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]'); + const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]'); expect(membersonly.length).toBe(1); expect(membersonly[0].getAttribute('type')).toBe('checkbox'); membersonly[0].checked = true; - var moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]'); + const moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]'); expect(moderated.length).toBe(1); expect(moderated[0].getAttribute('type')).toBe('checkbox'); moderated[0].checked = true; - var password = view.el.querySelectorAll('input[name="muc#roomconfig_roomsecret"]'); + const password = view.el.querySelectorAll('input[name="muc#roomconfig_roomsecret"]'); expect(password.length).toBe(1); expect(password[0].getAttribute('type')).toBe('password'); - var allowpm = view.el.querySelectorAll('select[name="muc#roomconfig_allowpm"]'); + const allowpm = view.el.querySelectorAll('select[name="muc#roomconfig_allowpm"]'); expect(allowpm.length).toBe(1); allowpm[0].value = 'moderators'; - var presencebroadcast = view.el.querySelectorAll('select[name="muc#roomconfig_presencebroadcast"]'); + const presencebroadcast = view.el.querySelectorAll('select[name="muc#roomconfig_presencebroadcast"]'); expect(presencebroadcast.length).toBe(1); presencebroadcast[0].value = ['moderator']; @@ -4167,9 +4167,9 @@ // Check in reverse order that we requested all three lists // (member, owner and admin). - var admin_iq_id = IQ_ids.pop(); - var owner_iq_id = IQ_ids.pop(); - var member_iq_id = IQ_ids.pop(); + const admin_iq_id = IQ_ids.pop(); + const owner_iq_id = IQ_ids.pop(); + const member_iq_id = IQ_ids.pop(); expect(sent_IQs.pop().toLocaleString()).toBe( ``+ diff --git a/spec/room_registration.js b/spec/room_registration.js index 8bb045b54..6e19f4329 100644 --- a/spec/room_registration.js +++ b/spec/room_registration.js @@ -78,90 +78,50 @@ it("allows you to automatically register your nickname when joining a room", mock.initConverse( null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true}, - function (done, _converse) { + async function (done, _converse) { - let view; const IQ_stanzas = _converse.connection.IQ_stanzas; const room_jid = 'coven@chat.shakespeare.lit'; - _converse.api.rooms.open(room_jid, {'nick': 'romeo'}) - .then(() => { - return test_utils.waitUntil(() => _.filter( - IQ_stanzas, - iq => iq.querySelector( - `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` - )).pop()); - }).then(stanza => { - const features_stanza = $iq({ - 'from': room_jid, - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/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'}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - return test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); - }).then(stanza => { - // 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, - 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(test_utils.createRequest(presence)); - return test_utils.waitUntil(() => _.filter( - _converse.connection.IQ_stanzas, - iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length - ).pop()); - }).then(stanza => { - expect(Strophe.serialize(stanza)) - .toBe(``+ - ``); - view = _converse.chatboxviews.get(room_jid); - const result = $iq({ - 'from': view.model.get('jid'), - 'id': stanza.getAttribute('id'), - 'to': _converse.bare_jid, - 'type': 'result', - }).c('query', {'type': 'jabber:iq:register'}) - .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'}) - .c('field', { - 'label': 'Desired Nickname', - 'type': 'text-single', - 'var': 'muc#register_roomnick' - }).c('required'); - _converse.connection._dataRecv(test_utils.createRequest(result)); - return test_utils.waitUntil(() => _.filter( - _converse.connection.IQ_stanzas, - iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length - ).pop()); - }).then(stanza => { - expect(Strophe.serialize(stanza)).toBe( - ``+ - ``+ - ``+ - `http://jabber.org/protocol/muc#register`+ - `romeo`+ - ``+ - ``+ - ``); - done(); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo'); + const view = _converse.chatboxviews.get(room_jid); + + let stanza = await test_utils.waitUntil(() => _.filter( + _converse.connection.IQ_stanzas, + iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length + ).pop()); + + expect(Strophe.serialize(stanza)) + .toBe(``+ + ``); + const result = $iq({ + 'from': view.model.get('jid'), + 'id': stanza.getAttribute('id'), + 'to': _converse.bare_jid, + 'type': 'result', + }).c('query', {'type': 'jabber:iq:register'}) + .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'}) + .c('field', { + 'label': 'Desired Nickname', + 'type': 'text-single', + 'var': 'muc#register_roomnick' + }).c('required'); + _converse.connection._dataRecv(test_utils.createRequest(result)); + stanza = await test_utils.waitUntil(() => _.filter( + _converse.connection.IQ_stanzas, + iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length + ).pop()); + + expect(Strophe.serialize(stanza)).toBe( + ``+ + ``+ + ``+ + `http://jabber.org/protocol/muc#register`+ + `romeo`+ + ``+ + ``+ + ``); + done(); })); }); }); diff --git a/spec/spoilers.js b/spec/spoilers.js index c7a44a0d0..4b2892f02 100644 --- a/spec/spoilers.js +++ b/spec/spoilers.js @@ -35,9 +35,8 @@ }).t(spoiler_hint) .tree(); await _converse.chatboxes.onMessage(msg); - - await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); const view = _converse.chatboxviews.get(sender_jid); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio') expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio'); const message_content = view.el.querySelector('.chat-msg__text'); @@ -70,8 +69,8 @@ 'xmlns': 'urn:xmpp:spoiler:0', }).tree(); await _converse.chatboxes.onMessage(msg); - await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); const view = _converse.chatboxviews.get(sender_jid); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio') expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Mercutio')).toBeTruthy(); const message_content = view.el.querySelector('.chat-msg__text'); diff --git a/src/converse-chatview.js b/src/converse-chatview.js index c599ed610..d94516856 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -171,10 +171,12 @@ converse.plugins.add('converse-chatview', { if (this.model.vcard) { this.model.vcard.on('change', this.debouncedRender, this); } - this.model.on('rosterContactAdded', () => { - this.model.contact.on('change:nickname', this.debouncedRender, this); - this.debouncedRender(); - }); + if (this.model.rosterContactAdded) { + this.model.rosterContactAdded.then(() => { + this.model.contact.on('change:nickname', this.debouncedRender, this); + this.debouncedRender(); + }); + } }, render () { @@ -222,7 +224,7 @@ converse.plugins.add('converse-chatview', { initialize () { _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.model.on('rosterContactAdded', this.registerContactEventHandlers, this); + this.model.rosterContactAdded.then(() => this.registerContactEventHandlers()); this.model.on('change', this.render, this); this.registerContactEventHandlers(); /** @@ -798,10 +800,8 @@ converse.plugins.add('converse-chatview', { async showMessage (message) { const view = this.add(message.get('id'), new _converse.MessageView({'model': message})); await view.render(); - // Clear chat state notifications sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content).forEach(u.removeElement); - this.insertMessage(view); this.insertDayIndicator(view.el); this.setScrollPosition(view.el); diff --git a/src/converse-message-view.js b/src/converse-message-view.js index 151bb5aa3..8b5353356 100644 --- a/src/converse-message-view.js +++ b/src/converse-message-view.js @@ -89,13 +89,26 @@ converse.plugins.add('converse-message-view', { this.render(); } }, 50); + if (this.model.vcard) { this.model.vcard.on('change', this.debouncedRender, this); } - this.model.on('rosterContactAdded', () => { - this.model.contact.on('change:nickname', this.debouncedRender, this); - this.debouncedRender(); - }); + + if (this.model.rosterContactAdded) { + this.model.rosterContactAdded.then(() => { + this.model.contact.on('change:nickname', this.debouncedRender, this); + this.debouncedRender(); + }); + } + + if (this.model.occupantAdded) { + this.model.occupantAdded.then(() => { + this.model.occupant.on('change:role', this.debouncedRender, this); + this.model.occupant.on('change:affiliation', this.debouncedRender, this); + this.debouncedRender(); + }); + } + this.model.on('change', this.onChanged, this); this.model.on('destroy', this.fadeOut, this); }, @@ -170,16 +183,17 @@ converse.plugins.add('converse-message-view', { }, async renderChatMessage () { - const is_me_message = this.isMeCommand(), - time = dayjs(this.model.get('time')), - role = this.model.vcard ? this.model.vcard.get('role') : null, - roles = role ? role.split(',') : []; + const is_me_message = this.isMeCommand(); + const time = dayjs(this.model.get('time')); + const role = this.model.vcard ? this.model.vcard.get('role') : null; + const roles = role ? role.split(',') : []; const msg = u.stringToElement(tpl_message( Object.assign( this.model.toJSON(), { '__': __, 'is_groupchat_message': this.model.get('type') === 'groupchat', + 'occupant': this.model.occupant, 'is_me_message': is_me_message, 'roles': roles, 'pretty_time': time.format(_converse.time_format), diff --git a/src/headless/converse-chatboxes.js b/src/headless/converse-chatboxes.js index bb3346fa6..b1a77c48b 100644 --- a/src/headless/converse-chatboxes.js +++ b/src/headless/converse-chatboxes.js @@ -59,12 +59,16 @@ converse.plugins.add('converse-chatboxes', { const ModelWithContact = Backbone.Model.extend({ + initialize () { + this.rosterContactAdded = u.getResolveablePromise(); + }, + async setRosterContact (jid) { const contact = await _converse.api.contacts.get(jid); if (contact) { this.contact = contact; this.set('nickname', contact.get('nickname')); - this.trigger('rosterContactAdded'); + this.rosterContactAdded.resolve(); } } }); @@ -88,10 +92,13 @@ converse.plugins.add('converse-chatboxes', { }, initialize () { + ModelWithContact.prototype.initialize.apply(this, arguments); + if (this.get('type') === 'chat') { this.setVCard(); this.setRosterContact(Strophe.getBareJidFromJid(this.get('from'))); } + if (this.get('file')) { this.on('change:put', this.uploadFile, this); } @@ -259,6 +266,8 @@ converse.plugins.add('converse-chatboxes', { }, initialize () { + ModelWithContact.prototype.initialize.apply(this, arguments); + const jid = this.get('jid'); if (!jid) { // XXX: The `validate` method will prevent this model diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index d148df808..9ee92b31f 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -247,11 +247,38 @@ converse.plugins.add('converse-muc', { */ _converse.ChatRoomMessage = _converse.Message.extend({ - getVCardForChatroomOccupant () { - const chatbox = this.collection.chatbox, - nick = Strophe.getResourceFromJid(this.get('from')); + initialize () { + if (this.get('file')) { + this.on('change:put', this.uploadFile, this); + } + if (this.isEphemeral()) { + window.setTimeout(() => { + try { + this.destroy() + } catch (e) { + _converse.log(e, Strophe.LogLevel.ERROR); + } + }, 10000); + } else { + this.occupantAdded = u.getResolveablePromise(); + this.setOccupant(); + this.setVCard(); + } + }, - if (chatbox.get('nick') === nick) { + setOccupant () { + const chatbox = _.get(this, 'collection.chatbox'); + if (!chatbox) { return; } + const nick = Strophe.getResourceFromJid(this.get('from')); + this.occupant = chatbox.occupants.findWhere({'nick': nick}); + this.occupantAdded.resolve(); + }, + + getVCardForChatroomOccupant () { + const chatbox = _.get(this, 'collection.chatbox'); + const nick = Strophe.getResourceFromJid(this.get('from')); + + if (chatbox && chatbox.get('nick') === nick) { return _converse.xmppstatus.vcard; } else { let vcard; @@ -260,14 +287,21 @@ converse.plugins.add('converse-muc', { } if (!vcard) { let jid; - const occupant = chatbox.occupants.findWhere({'nick': nick}); - if (occupant && occupant.get('jid')) { - jid = occupant.get('jid'); + if (this.occupant && this.occupant.get('jid')) { + jid = this.occupant.get('jid'); this.save({'vcard_jid': jid}, {'silent': true}); } else { jid = this.get('from'); } - vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid}); + if (jid) { + vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid}); + } else { + _converse.log( + `Could not assign VCard for message because no JID found! msgid: ${this.get('msgid')}`, + Strophe.LogLevel.ERROR + ); + return; + } } return vcard; } @@ -278,7 +312,7 @@ converse.plugins.add('converse-muc', { // VCards aren't supported return; } - if (this.get('type') === 'error') { + if (['error', 'info'].includes(this.get('type'))) { return; } else { this.vcard = this.getVCardForChatroomOccupant(); @@ -389,7 +423,6 @@ converse.plugins.add('converse-muc', { if (_converse.auto_register_muc_nickname && await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'))) { - this.registerNickname() } } @@ -641,7 +674,7 @@ converse.plugins.add('converse-muc', { [text, references] = this.parseTextForReferences(text); const origin_id = _converse.connection.getUniqueId(); - return this.addOccupantData({ + return { 'id': origin_id, 'msgid': origin_id, 'origin_id': origin_id, @@ -655,7 +688,7 @@ converse.plugins.add('converse-muc', { 'sender': 'me', 'spoiler_hint': is_spoiler ? spoiler_hint : undefined, 'type': 'groupchat' - }); + }; }, /** @@ -1426,17 +1459,6 @@ converse.plugins.add('converse-muc', { } }, - addOccupantData (attrs) { - if (attrs.nick) { - const occupant = this.occupants.findOccupant({'nick': attrs.nick}); - if (occupant) { - attrs['affiliation'] = occupant.get('affiliation'); - attrs['role'] = occupant.get('role'); - } - } - return attrs; - }, - /** * Handler for all MUC messages sent to this groupchat. * @private @@ -1460,13 +1482,12 @@ converse.plugins.add('converse-muc', { this.isChatMarker(stanza)) { return _converse.api.trigger('message', {'stanza': original_stanza}); } - let attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza); + const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza); if (attrs.nick && !this.subjectChangeHandled(attrs) && !this.ignorableCSN(attrs) && (attrs['chat_state'] || !u.isEmptyMessage(attrs))) { - attrs = this.addOccupantData(attrs); const msg = this.correctMessage(attrs) || this.messages.create(attrs); this.incrementUnreadMsgCounter(msg); } diff --git a/src/headless/converse-vcard.js b/src/headless/converse-vcard.js index c01fa3f72..2206ce102 100644 --- a/src/headless/converse-vcard.js +++ b/src/headless/converse-vcard.js @@ -130,8 +130,10 @@ converse.plugins.add('converse-vcard', { _converse.api.listen.on('statusInitialized', () => { const vcards = _converse.vcards; - const jid = _converse.session.get('bare_jid'); - _converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid}); + if (_converse.session) { + const jid = _converse.session.get('bare_jid'); + _converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid}); + } }); @@ -165,7 +167,7 @@ converse.plugins.add('converse-vcard', { * // Failure * }). */ - 'set' (jid, data) { + set (jid, data) { return setVCard(jid, data); }, diff --git a/src/templates/message.html b/src/templates/message.html index 892cc40d9..347e1d76e 100644 --- a/src/templates/message.html +++ b/src/templates/message.html @@ -6,7 +6,7 @@
{[ if (o.is_me_message) { ]}{[ } ]} - {[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}} + {[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}} {[ if (!o.is_me_message) { ]} {[o.roles.forEach(function (role) { ]} {{{role}}} {[ }); ]} diff --git a/tests/utils.js b/tests/utils.js index b660cd4c2..f419ae0b3 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -194,7 +194,7 @@ // 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 - var presence = $pres({ + const presence = $pres({ to: _converse.connection.jid, from: `${room_jid}/${nick}`, id: u.getUniqueId() @@ -207,6 +207,43 @@ .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 + 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 + ).pop()); + const member_list_stanza = $iq({ + 'from': 'coven@chat.shakespeare.lit', + 'id': member_IQ.getAttribute('id'), + 'to': 'romeo@montague.lit/orchard', + 'type': 'result' + }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}); + _converse.connection._dataRecv(utils.createRequest(member_list_stanza)); + + 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 + ).pop()); + const admin_list_stanza = $iq({ + 'from': 'coven@chat.shakespeare.lit', + 'id': admin_IQ.getAttribute('id'), + 'to': 'romeo@montague.lit/orchard', + 'type': 'result' + }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}); + _converse.connection._dataRecv(utils.createRequest(admin_list_stanza)); + + 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 + ).pop()); + const owner_list_stanza = $iq({ + 'from': 'coven@chat.shakespeare.lit', + 'id': owner_IQ.getAttribute('id'), + 'to': 'romeo@montague.lit/orchard', + 'type': 'result' + }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}); + _converse.connection._dataRecv(utils.createRequest(owner_list_stanza)); }; utils.clearBrowserStorage = function () {