Commit a7f28cd6 authored by JC Brand's avatar JC Brand

When creating message objects, wait for confirmation from storage

Queue messages and handle them sequentially, each time waiting for promises to
resolve before handling the next message.

Updates #1899, which likely happens because an error message is received
before messages have been fetched.
parent e691d858
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
it("shows all autocompletion options when the user presses @", it("shows all autocompletion options when the user presses @",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t('Hello world').tree(); }).c('body').t('Hello world').tree();
await view.model.onMessage(msg); await view.model.queueMessage(msg);
// Test that pressing @ brings up all options // Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea'); const textarea = view.el.querySelector('textarea.chat-textarea');
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
it("autocompletes when the user presses tab", it("autocompletes when the user presses tab",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
......
...@@ -17,18 +17,17 @@ ...@@ -17,18 +17,17 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
const toolbar = view.el.querySelector('ul.chat-toolbar'); const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1); expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('a.toggle-smiley').click(); toolbar.querySelector('a.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists'))); await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container')); const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'), 1000);
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a')); const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'), 1000);
item.click() item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: '); expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
toolbar.querySelector('a.toggle-smiley').click(); // Close the panel again toolbar.querySelector('a.toggle-smiley').click(); // Close the panel again
......
...@@ -15,14 +15,14 @@ ...@@ -15,14 +15,14 @@
describe("Discovering support", function () { describe("Discovering support", function () {
it("is done automatically", mock.initConverse(async (done, _converse) => { it("is done automatically",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const IQ_ids = _converse.connection.IQ_ids;
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []);
await u.waitUntil(() => _.filter( let selector = 'iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]';
IQ_stanzas, let stanza = await u.waitUntil(() => IQ_stanzas.find(iq => iq.querySelector(selector)), 1000);
iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
);
/* <iq type='result' /* <iq type='result'
* from='plays.shakespeare.lit' * from='plays.shakespeare.lit'
...@@ -37,16 +37,11 @@ ...@@ -37,16 +37,11 @@
* </query> * </query>
* </iq> * </iq>
*/ */
let stanza = _.find(IQ_stanzas, function (iq) {
return iq.querySelector(
'iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
});
const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({ stanza = $iq({
'type': 'result', 'type': 'result',
'from': 'montague.lit', 'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard', 'to': 'romeo@montague.lit/orchard',
'id': info_IQ_id 'id': stanza.getAttribute('id'),
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', { .c('identity', {
'category': 'server', 'category': 'server',
...@@ -59,19 +54,16 @@ ...@@ -59,19 +54,16 @@
let entities = await _converse.api.disco.entities.get(); let entities = await _converse.api.disco.entities.get();
expect(entities.length).toBe(2); expect(entities.length).toBe(2);
expect(_.includes(entities.pluck('jid'), 'montague.lit')).toBe(true); expect(entities.pluck('jid').includes('montague.lit')).toBe(true);
expect(_.includes(entities.pluck('jid'), 'romeo@montague.lit')).toBe(true); expect(entities.pluck('jid').includes('romeo@montague.lit')).toBe(true);
expect(entities.get(_converse.domain).features.length).toBe(2); expect(entities.get(_converse.domain).features.length).toBe(2);
expect(entities.get(_converse.domain).identities.length).toBe(1); expect(entities.get(_converse.domain).identities.length).toBe(1);
// Converse.js sees that the entity has a disco#items feature, // Converse.js sees that the entity has a disco#items feature,
// so it will make a query for it. // so it will make a query for it.
await u.waitUntil(() => _.filter( selector = 'iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]';
IQ_stanzas, await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(selector)).length, 1000);
iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]')
).length
);
/* <iq from='montague.tld' /* <iq from='montague.tld'
* id='step_01' * id='step_01'
* to='romeo@montague.tld/garden' * to='romeo@montague.tld/garden'
...@@ -82,15 +74,13 @@ ...@@ -82,15 +74,13 @@
* </query> * </query>
* </iq> * </iq>
*/ */
stanza = _.find(IQ_stanzas, function (iq) { selector = 'iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]';
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]'); stanza = IQ_stanzas.find(iq => iq.querySelector(selector), 500);
});
const items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({ stanza = $iq({
'type': 'result', 'type': 'result',
'from': 'montague.lit', 'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard', 'to': 'romeo@montague.lit/orchard',
'id': items_IQ_id 'id': stanza.getAttribute('id'),
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'}) }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
.c('item', { .c('item', {
'jid': 'upload.montague.lit', 'jid': 'upload.montague.lit',
...@@ -98,28 +88,18 @@ ...@@ -98,28 +88,18 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.api.disco.entities.get().then(function (entities) { _converse.api.disco.entities.get().then(entities => {
expect(entities.length).toBe(2); expect(entities.length).toBe(2);
expect(entities.get('montague.lit').items.length).toBe(1); expect(entities.get('montague.lit').items.length).toBe(1);
return u.waitUntil(function () { // Converse.js sees that the entity has a disco#info feature, so it will make a query for it.
// Converse.js sees that the entity has a disco#info feature, const selector = 'iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]';
// so it will make a query for it. return u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(selector)).length > 0);
return _.filter(IQ_stanzas, function (iq) {
return iq.querySelector('iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
}).length > 0;
}, 300);
}); });
stanza = await u.waitUntil(() => selector = 'iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]';
_.filter( stanza = await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(selector)).pop(), 1000);
IQ_stanzas,
iq => iq.querySelector('iq[to="upload.montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')
).pop()
);
const IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
expect(Strophe.serialize(stanza)).toBe( expect(Strophe.serialize(stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="`+IQ_id+`" to="upload.montague.lit" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit/orchard" id="`+stanza.getAttribute('id')+`" to="upload.montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/>`+ `<query xmlns="http://jabber.org/protocol/disco#info"/>`+
`</iq>`); `</iq>`);
...@@ -144,7 +124,7 @@ ...@@ -144,7 +124,7 @@
* </query> * </query>
* </iq> * </iq>
*/ */
stanza = $iq({'type': 'result', 'to': 'romeo@montague.lit/orchard', 'id': IQ_id, 'from': 'upload.montague.lit'}) stanza = $iq({'type': 'result', 'to': 'romeo@montague.lit/orchard', 'id': stanza.getAttribute('id'), 'from': 'upload.montague.lit'})
.c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) .c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up() .c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up()
.c('feature', {'var':'urn:xmpp:http:upload:0'}).up() .c('feature', {'var':'urn:xmpp:http:upload:0'}).up()
......
...@@ -296,7 +296,7 @@ ...@@ -296,7 +296,7 @@
</message>`); </message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough(); spyOn(view.model, 'getDuplicateMessage').and.callThrough();
spyOn(view.model, 'updateMessage').and.callThrough(); spyOn(view.model, 'updateMessage').and.callThrough();
view.model.onMessage(stanza); view.model.queueMessage(stanza);
await u.waitUntil(() => view.model.getDuplicateMessage.calls.count()); await u.waitUntil(() => view.model.getDuplicateMessage.calls.count());
expect(view.model.getDuplicateMessage.calls.count()).toBe(1); expect(view.model.getDuplicateMessage.calls.count()).toBe(1);
const result = view.model.getDuplicateMessage.calls.all()[0].returnValue const result = view.model.getDuplicateMessage.calls.all()[0].returnValue
...@@ -340,7 +340,7 @@ ...@@ -340,7 +340,7 @@
</result> </result>
</message>`); </message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough(); spyOn(view.model, 'getDuplicateMessage').and.callThrough();
view.model.onMessage(stanza); view.model.queueMessage(stanza);
await u.waitUntil(() => view.model.getDuplicateMessage.calls.count()); await u.waitUntil(() => view.model.getDuplicateMessage.calls.count());
expect(view.model.getDuplicateMessage.calls.count()).toBe(1); expect(view.model.getDuplicateMessage.calls.count()).toBe(1);
const result = await view.model.getDuplicateMessage.calls.all()[0].returnValue const result = await view.model.getDuplicateMessage.calls.all()[0].returnValue
...@@ -370,7 +370,7 @@ ...@@ -370,7 +370,7 @@
</forwarded> </forwarded>
</result> </result>
</message>`); </message>`);
view.model.onMessage(stanza); view.model.queueMessage(stanza);
await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
...@@ -390,7 +390,7 @@ ...@@ -390,7 +390,7 @@
</message>`); </message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough(); spyOn(view.model, 'getDuplicateMessage').and.callThrough();
view.model.onMessage(stanza); view.model.queueMessage(stanza);
await u.waitUntil(() => view.model.getDuplicateMessage.calls.count()); await u.waitUntil(() => view.model.getDuplicateMessage.calls.count());
expect(view.model.getDuplicateMessage.calls.count()).toBe(1); expect(view.model.getDuplicateMessage.calls.count()).toBe(1);
const result = await view.model.getDuplicateMessage.calls.all()[0].returnValue const result = await view.model.getDuplicateMessage.calls.all()[0].returnValue
......
...@@ -377,7 +377,7 @@ ...@@ -377,7 +377,7 @@
.c('body').t("Older message").up() .c('body').t("Older message").up()
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'})
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
msg = $msg({ msg = $msg({
...@@ -389,7 +389,7 @@ ...@@ -389,7 +389,7 @@
.c('body').t("Inbetween message").up() .c('body').t("Inbetween message").up()
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'})
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
msg = $msg({ msg = $msg({
...@@ -401,7 +401,7 @@ ...@@ -401,7 +401,7 @@
.c('body').t("another inbetween message").up() .c('body').t("another inbetween message").up()
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'})
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
msg = $msg({ msg = $msg({
...@@ -413,7 +413,7 @@ ...@@ -413,7 +413,7 @@
.c('body').t("An earlier message on the next day").up() .c('body').t("An earlier message on the next day").up()
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'})
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
msg = $msg({ msg = $msg({
...@@ -425,7 +425,7 @@ ...@@ -425,7 +425,7 @@
.c('body').t("newer message from the next day").up() .c('body').t("newer message from the next day").up()
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'})
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
// Insert <composing> message, to also check that // Insert <composing> message, to also check that
...@@ -439,7 +439,8 @@ ...@@ -439,7 +439,8 @@
'type': 'chat'}) 'type': 'chat'})
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up() .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree(); .tree();
await _converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
msg = $msg({ msg = $msg({
'id': _converse.connection.getUniqueId(), 'id': _converse.connection.getUniqueId(),
...@@ -509,9 +510,9 @@ ...@@ -509,9 +510,9 @@
})); }));
it("is ignored if it's a malformed headline message", it("is ignored if it's a malformed headline message",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse); await test_utils.openControlBox(_converse);
...@@ -569,6 +570,7 @@ ...@@ -569,6 +570,7 @@
'type': 'chat' 'type': 'chat'
}).c('body').t(msgtext).tree(); }).c('body').t(msgtext).tree();
console.log('AAAAAAAAAAAAAAAAAAAAAAAAAAA')
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
const chatbox = _converse.chatboxes.get(sender_jid); const chatbox = _converse.chatboxes.get(sender_jid);
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
...@@ -576,7 +578,8 @@ ...@@ -576,7 +578,8 @@
expect(chatbox).toBeDefined(); expect(chatbox).toBeDefined();
expect(view).toBeDefined(); expect(view).toBeDefined();
// Check that the message was received and check the message parameters // Check that the message was received and check the message parameters
expect(chatbox.messages.length).toEqual(1); console.log('BBBBBBBBBBBBBBBBBBBBBBBBBBB')
await u.waitUntil(() => chatbox.messages.length);
const msg_obj = chatbox.messages.models[0]; const msg_obj = chatbox.messages.models[0];
expect(msg_obj.get('message')).toEqual(msgtext); expect(msg_obj.get('message')).toEqual(msgtext);
expect(msg_obj.get('fullname')).toBeUndefined(); expect(msg_obj.get('fullname')).toBeUndefined();
...@@ -584,10 +587,13 @@ ...@@ -584,10 +587,13 @@
expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(false); expect(msg_obj.get('is_delayed')).toEqual(false);
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
await new Promise(resolve => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
console.log('CCCCCCCCCCCCCCCCCCCCCCCCCCC')
await u.waitUntil(() => chat_content.querySelector('.chat-msg .chat-msg__text'));
expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext); expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
console.log('DDDDDDDDDDDDDDDDDDDDDDDDDDD')
await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet') await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); expect(chat_content.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
done(); done();
...@@ -624,7 +630,6 @@ ...@@ -624,7 +630,6 @@
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
const chatbox = await _converse.api.chats.get(recipient_jid); const chatbox = await _converse.api.chats.get(recipient_jid);
const view = _converse.api.chatviews.get(recipient_jid); const view = _converse.api.chatviews.get(recipient_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(chatbox).toBeDefined(); expect(chatbox).toBeDefined();
expect(view).toBeDefined(); expect(view).toBeDefined();
...@@ -1405,6 +1410,7 @@ ...@@ -1405,6 +1410,7 @@
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the message was received and check the message parameters // Check that the message was received and check the message parameters
await u.waitUntil(() => chatbox.messages.length);
expect(chatbox.messages.length).toEqual(1); expect(chatbox.messages.length).toEqual(1);
const msg_obj = chatbox.messages.models[0]; const msg_obj = chatbox.messages.models[0];
expect(msg_obj.get('message')).toEqual(message); expect(msg_obj.get('message')).toEqual(message);
...@@ -1440,6 +1446,7 @@ ...@@ -1440,6 +1446,7 @@
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree() .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
); );
const view = _converse.api.chatviews.get(sender_jid); const view = _converse.api.chatviews.get(sender_jid);
await u.waitUntil(() => view.model.messages.length);
expect(view.model.messages.length).toEqual(1); expect(view.model.messages.length).toEqual(1);
const msg_obj = view.model.messages.at(0); const msg_obj = view.model.messages.at(0);
expect(msg_obj.get('message')).toEqual(message.trim()); expect(msg_obj.get('message')).toEqual(message.trim());
...@@ -1544,15 +1551,12 @@ ...@@ -1544,15 +1551,12 @@
expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined(); expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
const view = await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
await new Promise(resolve => view.once('messageInserted', resolve));
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
const chatbox = await _converse.api.chats.get(sender_jid); const chatbox = await _converse.api.chats.get(sender_jid);
const view = _converse.api.chatviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(chatbox).toBeDefined();
expect(view).toBeDefined();
expect(chatbox.get('fullname') === sender_jid); expect(chatbox.get('fullname') === sender_jid);
await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio'); await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
...@@ -1593,12 +1597,10 @@ ...@@ -1593,12 +1597,10 @@
let chatbox = await _converse.api.chats.get(sender_jid); let chatbox = await _converse.api.chats.get(sender_jid);
expect(chatbox).toBe(null); expect(chatbox).toBe(null);
// onMessage is a handler for received XMPP messages
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
let view = _converse.chatboxviews.get(sender_jid); let view = _converse.chatboxviews.get(sender_jid);
expect(view).not.toBeDefined(); expect(view).not.toBeDefined();
// onMessage is a handler for received XMPP messages
_converse.allow_non_roster_messaging = true; _converse.allow_non_roster_messaging = true;
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
view = _converse.chatboxviews.get(sender_jid); view = _converse.chatboxviews.get(sender_jid);
......
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); view.model.queueMessage(msg);
await u.waitUntil(() => view.model.messages.length); await u.waitUntil(() => view.model.messages.length);
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy(); expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1'); expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
......
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
describe("The groupchat moderator tool", function () { describe("The groupchat moderator tool", function () {
it("allows you to set affiliations and roles", it("allows you to set affiliations and roles",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough(); spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit'; const muc_jid = 'lounge@montague.lit';
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
]; ];
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members); await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5)); await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
const textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools'; textarea.value = '/modtools';
......
This diff is collapsed.
This diff is collapsed.
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
await view.model.onMessage(message.nodeTree); await view.model.queueMessage(message.nodeTree);
await u.waitUntil(() => _converse.playSoundNotification.calls.count()); await u.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled(); expect(_converse.playSoundNotification).toHaveBeenCalled();
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
await view.model.onMessage(message.nodeTree); await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1); expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false; _converse.play_sounds = false;
...@@ -199,7 +199,7 @@ ...@@ -199,7 +199,7 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
await view.model.onMessage(message.nodeTree); await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1); expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false; _converse.play_sounds = false;
done(); done();
......
...@@ -353,6 +353,7 @@ ...@@ -353,6 +353,7 @@
// Hide the controlbox so that we can see whether the test // Hide the controlbox so that we can see whether the test
// passed or failed // passed or failed
u.addClass('hidden', _converse.chatboxviews.get('controlbox').el); u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);
delete _converse.connection;
done(); done();
})); }));
}); });
......
This diff is collapsed.
...@@ -283,7 +283,7 @@ ...@@ -283,7 +283,7 @@
const view = _converse.chatboxviews.get(room_jid); const view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true}); view.model.set({'minimized': true});
const nick = mock.chatroom_names[0]; const nick = mock.chatroom_names[0];
await view.model.onMessage( await view.model.queueMessage(
$msg({ $msg({
from: room_jid+'/'+nick, from: room_jid+'/'+nick,
id: u.getUniqueId(), id: u.getUniqueId(),
...@@ -297,7 +297,7 @@ ...@@ -297,7 +297,7 @@
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy(); expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated // If the user is mentioned, the counter also gets updated
await view.model.onMessage( await view.model.queueMessage(
$msg({ $msg({
from: room_jid+'/'+nick, from: room_jid+'/'+nick,
id: u.getUniqueId(), id: u.getUniqueId(),
...@@ -310,7 +310,7 @@ ...@@ -310,7 +310,7 @@
expect(indicator_el.textContent).toBe('1'); expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough(); spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.onMessage( await view.model.queueMessage(
$msg({ $msg({
from: room_jid+'/'+nick, from: room_jid+'/'+nick,
id: u.getUniqueId(), id: u.getUniqueId(),
......
...@@ -189,7 +189,7 @@ converse.plugins.add('converse-omemo', { ...@@ -189,7 +189,7 @@ converse.plugins.add('converse-omemo', {
let message, stanza; let message, stanza;
try { try {
const devices = await _converse.getBundlesAndBuildSessions(this); const devices = await _converse.getBundlesAndBuildSessions(this);
message = this.messages.create(attrs); message = await this.createMessage(attrs);
stanza = await _converse.createOMEMOMessageStanza(this, message, devices); stanza = await _converse.createOMEMOMessageStanza(this, message, devices);
} catch (e) { } catch (e) {
this.handleMessageSendError(e); this.handleMessageSendError(e);
...@@ -307,7 +307,7 @@ converse.plugins.add('converse-omemo', { ...@@ -307,7 +307,7 @@ converse.plugins.add('converse-omemo', {
reportDecryptionError (e) { reportDecryptionError (e) {
if (_converse.loglevel === 'debug') { if (_converse.loglevel === 'debug') {
const { __ } = _converse; const { __ } = _converse;
this.messages.create({ this.createMessage({
'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`, 'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`,
'type': 'error', 'type': 'error',
}); });
...@@ -1208,11 +1208,11 @@ converse.plugins.add('converse-omemo', { ...@@ -1208,11 +1208,11 @@ converse.plugins.add('converse-omemo', {
if (chatroom.get('omemo_active')) { if (chatroom.get('omemo_active')) {
const supported = await _converse.contactHasOMEMOSupport(occupant.get('jid')); const supported = await _converse.contactHasOMEMOSupport(occupant.get('jid'));
if (!supported) { if (!supported) {
chatroom.messages.create({ chatroom.createMessage({
'message': __("%1$s doesn't appear to have a client that supports OMEMO. " + 'message': __("%1$s doesn't appear to have a client that supports OMEMO. " +
"Encrypted chat will no longer be possible in this grouchat.", occupant.get('nick')), "Encrypted chat will no longer be possible in this grouchat.", occupant.get('nick')),
'type': 'error' 'type': 'error'
}); });
chatroom.save({'omemo_active': false, 'omemo_supported': false}); chatroom.save({'omemo_active': false, 'omemo_supported': false});
} }
} }
......
...@@ -383,6 +383,19 @@ converse.plugins.add('converse-chat', { ...@@ -383,6 +383,19 @@ converse.plugins.add('converse-chat', {
return this.messages.fetched; return this.messages.fetched;
}, },
/**
* Queue an incoming `chat` message stanza for processing.
* @async
* @private
* @method _converse.ChatRoom#queueMessage
* @param { XMLElement } stanza - The message stanza.
*/
queueMessage (stanza, original_stanza, from_jid) {
this.msg_chain = (this.msg_chain || this.messages.fetched);
this.msg_chain = this.msg_chain.then(() => this.onMessage(stanza, original_stanza, from_jid));
return this.msg_chain;
},
async onMessage (stanza, original_stanza, from_jid) { async onMessage (stanza, original_stanza, from_jid) {
const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza); const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
const message = this.getDuplicateMessage(attrs); const message = this.getDuplicateMessage(attrs);
...@@ -392,12 +405,12 @@ converse.plugins.add('converse-chat', { ...@@ -392,12 +405,12 @@ converse.plugins.add('converse-chat', {
!this.handleReceipt (stanza, original_stanza, from_jid) && !this.handleReceipt (stanza, original_stanza, from_jid) &&
!this.handleChatMarker(stanza, from_jid) !this.handleChatMarker(stanza, from_jid)
) { ) {
if (this.handleRetraction(attrs)) { if (await this.handleRetraction(attrs)) {
return; return;
} }
this.setEditable(attrs, attrs.time, stanza); this.setEditable(attrs, attrs.time, stanza);
if (u.shouldCreateMessage(attrs)) { if (u.shouldCreateMessage(attrs)) {
const msg = this.handleCorrection(attrs) || this.messages.create(attrs); const msg = this.handleCorrection(attrs) || await this.createMessage(attrs);
this.incrementUnreadMsgCounter(msg); this.incrementUnreadMsgCounter(msg);
} }
} }
...@@ -468,9 +481,9 @@ converse.plugins.add('converse-chat', { ...@@ -468,9 +481,9 @@ converse.plugins.add('converse-chat', {
} }
}, },
createMessageFromError (error) { async createMessageFromError (error) {
if (error instanceof _converse.TimeoutError) { if (error instanceof _converse.TimeoutError) {
const msg = this.messages.create({'type': 'error', 'message': error.message, 'retry': true}); const msg = await this.createMessage({'type': 'error', 'message': error.message, 'retry': true});
msg.error = error; msg.error = error;
} }
}, },
...@@ -609,7 +622,7 @@ converse.plugins.add('converse-chat', { ...@@ -609,7 +622,7 @@ converse.plugins.add('converse-chat', {
* @returns { Boolean } Returns `true` or `false` depending on * @returns { Boolean } Returns `true` or `false` depending on
* whether a message was retracted or not. * whether a message was retracted or not.
*/ */
handleRetraction (attrs) { async handleRetraction (attrs) {
const RETRACTION_ATTRIBUTES = ['retracted', 'retracted_id', 'editable']; const RETRACTION_ATTRIBUTES = ['retracted', 'retracted_id', 'editable'];
if (attrs.retracted) { if (attrs.retracted) {
if (attrs.is_tombstone) { if (attrs.is_tombstone) {
...@@ -618,7 +631,7 @@ converse.plugins.add('converse-chat', { ...@@ -618,7 +631,7 @@ converse.plugins.add('converse-chat', {
const message = this.messages.findWhere({'origin_id': attrs.retracted_id, 'from': attrs.from}); const message = this.messages.findWhere({'origin_id': attrs.retracted_id, 'from': attrs.from});
if (!message) { if (!message) {
attrs['dangling_retraction'] = true; attrs['dangling_retraction'] = true;
this.messages.create(attrs); await this.createMessage(attrs);
return true; return true;
} }
message.save(pick(attrs, RETRACTION_ATTRIBUTES)); message.save(pick(attrs, RETRACTION_ATTRIBUTES));
...@@ -932,6 +945,10 @@ converse.plugins.add('converse-chat', { ...@@ -932,6 +945,10 @@ converse.plugins.add('converse-chat', {
} }
}, },
createMessage (attrs, options) {
return this.messages.create(attrs, Object.assign({'wait': true, 'promise':true}, options));
},
/** /**
* Responsible for sending off a text message inside an ongoing chat conversation. * Responsible for sending off a text message inside an ongoing chat conversation.
* @method _converse.ChatBox#sendMessage * @method _converse.ChatBox#sendMessage
...@@ -943,7 +960,7 @@ converse.plugins.add('converse-chat', { ...@@ -943,7 +960,7 @@ converse.plugins.add('converse-chat', {
* const chat = _converse.api.chats.get('buddy1@example.com'); * const chat = _converse.api.chats.get('buddy1@example.com');
* chat.sendMessage('hello world'); * chat.sendMessage('hello world');
*/ */
sendMessage (text, spoiler_hint) { async sendMessage (text, spoiler_hint) {
const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint); const attrs = this.getOutgoingMessageAttributes(text, spoiler_hint);
let message = this.messages.findWhere('correcting') let message = this.messages.findWhere('correcting')
if (message) { if (message) {
...@@ -961,7 +978,7 @@ converse.plugins.add('converse-chat', { ...@@ -961,7 +978,7 @@ converse.plugins.add('converse-chat', {
}); });
} else { } else {
this.setEditable(attrs, (new Date()).toISOString()); this.setEditable(attrs, (new Date()).toISOString());
message = this.messages.create(attrs); message = await this.createMessage(attrs);
} }
_converse.api.send(this.createMessageStanza(message)); _converse.api.send(this.createMessageStanza(message));
return message; return message;
...@@ -996,7 +1013,7 @@ converse.plugins.add('converse-chat', { ...@@ -996,7 +1013,7 @@ converse.plugins.add('converse-chat', {
const result = await _converse.api.disco.features.get(Strophe.NS.HTTPUPLOAD, _converse.domain); const result = await _converse.api.disco.features.get(Strophe.NS.HTTPUPLOAD, _converse.domain);
const item = result.pop(); const item = result.pop();
if (!item) { if (!item) {
this.messages.create({ this.createMessage({
'message': __("Sorry, looks like file upload is not supported by your server."), 'message': __("Sorry, looks like file upload is not supported by your server."),
'type': 'error', 'type': 'error',
'is_ephemeral': true 'is_ephemeral': true
...@@ -1008,16 +1025,16 @@ converse.plugins.add('converse-chat', { ...@@ -1008,16 +1025,16 @@ converse.plugins.add('converse-chat', {
slot_request_url = get(item, 'id'); slot_request_url = get(item, 'id');
if (!slot_request_url) { if (!slot_request_url) {
this.messages.create({ this.createMessage({
'message': __("Sorry, looks like file upload is not supported by your server."), 'message': __("Sorry, looks like file upload is not supported by your server."),
'type': 'error', 'type': 'error',
'is_ephemeral': true 'is_ephemeral': true
}); });
return; return;
} }
Array.from(files).forEach(file => { Array.from(files).forEach(async file => {
if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) { if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) {
return this.messages.create({ return this.createMessage({
'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.', 'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.',
file.name, filesize(max_file_size)), file.name, filesize(max_file_size)),
'type': 'error', 'type': 'error',
...@@ -1031,7 +1048,7 @@ converse.plugins.add('converse-chat', { ...@@ -1031,7 +1048,7 @@ converse.plugins.add('converse-chat', {
'slot_request_url': slot_request_url 'slot_request_url': slot_request_url
}); });
this.setEditable(attrs, (new Date()).toISOString()); this.setEditable(attrs, (new Date()).toISOString());
const message = this.messages.create(attrs, {'silent': true}); const message = await this.createMessage(attrs, {'silent': true});
message.file = file; message.file = file;
this.messages.trigger('add', message); this.messages.trigger('add', message);
message.getRequestSlotURL(); message.getRequestSlotURL();
...@@ -1129,7 +1146,7 @@ converse.plugins.add('converse-chat', { ...@@ -1129,7 +1146,7 @@ converse.plugins.add('converse-chat', {
return; return;
} }
const attrs = await chatbox.getMessageAttributesFromStanza(stanza, stanza); const attrs = await chatbox.getMessageAttributesFromStanza(stanza, stanza);
await chatbox.messages.create(attrs); await chatbox.createMessage(attrs);
} }
...@@ -1198,7 +1215,7 @@ converse.plugins.add('converse-chat', { ...@@ -1198,7 +1215,7 @@ converse.plugins.add('converse-chat', {
const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`, stanza).length > 0; const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`, stanza).length > 0;
const roster_nick = get(contact, 'attributes.nickname'); const roster_nick = get(contact, 'attributes.nickname');
const chatbox = await _converse.api.chats.get(contact_jid, {'nickname': roster_nick}, has_body); const chatbox = await _converse.api.chats.get(contact_jid, {'nickname': roster_nick}, has_body);
chatbox && await chatbox.onMessage(stanza, original_stanza, from_jid); chatbox && await chatbox.queueMessage(stanza, original_stanza, from_jid);
/** /**
* Triggered when a message stanza is been received and processed. * Triggered when a message stanza is been received and processed.
* @event _converse#message * @event _converse#message
...@@ -1286,7 +1303,7 @@ converse.plugins.add('converse-chat', { ...@@ -1286,7 +1303,7 @@ converse.plugins.add('converse-chat', {
_converse.api.listen.on('clearSession', () => { _converse.api.listen.on('clearSession', () => {
if (_converse.shouldClearCache()) { if (_converse.shouldClearCache()) {
_converse.chatboxes.filter(c => c.messages && c.messages.clearStore({'silent': true})); return Promise.all(_converse.chatboxes.map(c => c.messages && c.messages.clearStore({'silent': true})));
} }
}); });
/************************ END Event Handlers ************************/ /************************ END Event Handlers ************************/
......
...@@ -99,7 +99,7 @@ converse.plugins.add('converse-headlines', { ...@@ -99,7 +99,7 @@ converse.plugins.add('converse-headlines', {
'from': from_jid 'from': from_jid
}); });
const attrs = await chatbox.getMessageAttributesFromStanza(message, message); const attrs = await chatbox.getMessageAttributesFromStanza(message, message);
await chatbox.messages.create(attrs); await chatbox.createMessage(attrs);
_converse.api.trigger('message', {'chatbox': chatbox, 'stanza': message}); _converse.api.trigger('message', {'chatbox': chatbox, 'stanza': message});
} }
} }
......
...@@ -88,9 +88,7 @@ converse.plugins.add('converse-mam', { ...@@ -88,9 +88,7 @@ converse.plugins.add('converse-mam', {
if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) { if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) {
return; return;
} }
const message_handler = is_groupchat ? const msg_handler = is_groupchat ? s => this.queueMessage(s) : s => _converse.handleMessageStanza(s);
this.onMessage.bind(this) :
_converse.handleMessageStanza.bind(_converse.chatboxes);
const query = Object.assign({ const query = Object.assign({
'groupchat': is_groupchat, 'groupchat': is_groupchat,
...@@ -102,7 +100,7 @@ converse.plugins.add('converse-mam', { ...@@ -102,7 +100,7 @@ converse.plugins.add('converse-mam', {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
for (const message of result.messages) { for (const message of result.messages) {
try { try {
await message_handler(message); await msg_handler(message);
} catch (e) { } catch (e) {
log.error(e); log.error(e);
} }
......
...@@ -462,7 +462,7 @@ converse.plugins.add('converse-muc', { ...@@ -462,7 +462,7 @@ converse.plugins.add('converse-muc', {
}, },
async clearMessageQueue () { async clearMessageQueue () {
await Promise.all(this.message_queue.map(m => this.onMessage(m))); await Promise.all(this.message_queue.map(m => this.queueMessage(m)));
this.message_queue = []; this.message_queue = [];
}, },
...@@ -573,7 +573,7 @@ converse.plugins.add('converse-muc', { ...@@ -573,7 +573,7 @@ converse.plugins.add('converse-muc', {
log.warn(`received a mam message with type "chat".`); log.warn(`received a mam message with type "chat".`);
return true; return true;
} }
this.onMessage(stanza); this.queueMessage(stanza);
return true; return true;
}, null, 'message', 'groupchat', null, room_jid, }, null, 'message', 'groupchat', null, room_jid,
{'matchBareFromJid': true} {'matchBareFromJid': true}
...@@ -1766,7 +1766,7 @@ converse.plugins.add('converse-muc', { ...@@ -1766,7 +1766,7 @@ converse.plugins.add('converse-muc', {
* @returns { Boolean } Returns `true` or `false` depending on * @returns { Boolean } Returns `true` or `false` depending on
* whether a message was moderated or not. * whether a message was moderated or not.
*/ */
handleModeration (attrs) { async handleModeration (attrs) {
const MODERATION_ATTRIBUTES = [ const MODERATION_ATTRIBUTES = [
'editable', 'editable',
'moderated', 'moderated',
...@@ -1781,7 +1781,7 @@ converse.plugins.add('converse-muc', { ...@@ -1781,7 +1781,7 @@ converse.plugins.add('converse-muc', {
const message = this.messages.findWhere(query); const message = this.messages.findWhere(query);
if (!message) { if (!message) {
attrs['dangling_moderation'] = true; attrs['dangling_moderation'] = true;
this.messages.create(attrs); await this.createMessage(attrs);
return true; return true;
} }
message.save(pick(attrs, MODERATION_ATTRIBUTES)); message.save(pick(attrs, MODERATION_ATTRIBUTES));
...@@ -1801,19 +1801,32 @@ converse.plugins.add('converse-muc', { ...@@ -1801,19 +1801,32 @@ converse.plugins.add('converse-muc', {
}, },
/** /**
* Handler for all MUC messages sent to this groupchat. * Queue an incoming message stanza meant for this {@link _converse.Chatroom} for processing.
* @async
* @private * @private
* @method _converse.ChatRoom#onMessage * @method _converse.ChatRoom#queueMessage
* @param { XMLElement } stanza - The message stanza. * @param { XMLElement } stanza - The message stanza.
*/ */
async onMessage (stanza) { queueMessage (stanza) {
if (!this.messages.fetched || this.messages.fetched.isPending) { if (this.messages.fetched) {
// We're not ready to accept messages before we've fetched this.msg_chain = (this.msg_chain || this.messages.fetched);
// from our store, so we stuff them into a queue. this.msg_chain = this.msg_chain.then(() => this.onMessage(stanza));
return this.msg_chain;
} else {
this.message_queue.push(stanza); this.message_queue.push(stanza);
return; return Promise.resolve();
} }
},
/**
* Handler for all MUC messages sent to this groupchat. This method
* shouldn't be called directly, instead {@link _converse.ChatRoom#queueMessage}
* should be called.
* @private
* @method _converse.ChatRoom#onMessage
* @param { XMLElement } stanza - The message stanza.
*/
async onMessage (stanza) {
if (sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length) { if (sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length) {
return log.warn('onMessage: Ignoring unencapsulated forwarded groupchat message'); return log.warn('onMessage: Ignoring unencapsulated forwarded groupchat message');
} }
...@@ -1832,7 +1845,7 @@ converse.plugins.add('converse-muc', { ...@@ -1832,7 +1845,7 @@ converse.plugins.add('converse-muc', {
return log.warn(`onMessage: Ignoring alleged MAM groupchat message from ${stanza.getAttribute('from')}`); return log.warn(`onMessage: Ignoring alleged MAM groupchat message from ${stanza.getAttribute('from')}`);
} }
} }
this.createInfoMessages(stanza); await this.createInfoMessages(stanza);
this.fetchFeaturesIfConfigurationChanged(stanza); this.fetchFeaturesIfConfigurationChanged(stanza);
const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza); const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
...@@ -1844,8 +1857,8 @@ converse.plugins.add('converse-muc', { ...@@ -1844,8 +1857,8 @@ converse.plugins.add('converse-muc', {
return _converse.api.trigger('message', {'stanza': original_stanza}); return _converse.api.trigger('message', {'stanza': original_stanza});
} }
if (this.handleRetraction(attrs) || if (await this.handleRetraction(attrs) ||
this.handleModeration(attrs) || await this.handleModeration(attrs) ||
this.subjectChangeHandled(attrs) || this.subjectChangeHandled(attrs) ||
this.ignorableCSN(attrs)) { this.ignorableCSN(attrs)) {
return _converse.api.trigger('message', {'stanza': original_stanza}); return _converse.api.trigger('message', {'stanza': original_stanza});
...@@ -1853,7 +1866,7 @@ converse.plugins.add('converse-muc', { ...@@ -1853,7 +1866,7 @@ converse.plugins.add('converse-muc', {
this.setEditable(attrs, attrs.time); this.setEditable(attrs, attrs.time);
if (u.shouldCreateGroupchatMessage(attrs)) { if (u.shouldCreateGroupchatMessage(attrs)) {
const msg = this.handleCorrection(attrs) || await this.messages.create(attrs, {promise: true}); const msg = this.handleCorrection(attrs) || await this.createMessage(attrs);
this.incrementUnreadMsgCounter(msg); this.incrementUnreadMsgCounter(msg);
} }
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this}); _converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this});
...@@ -1870,7 +1883,7 @@ converse.plugins.add('converse-muc', { ...@@ -1870,7 +1883,7 @@ converse.plugins.add('converse-muc', {
'message': text, 'message': text,
'is_ephemeral': true 'is_ephemeral': true
} }
this.messages.create(attrs); this.createMessage(attrs);
} }
} }
}, },
...@@ -1960,7 +1973,7 @@ converse.plugins.add('converse-muc', { ...@@ -1960,7 +1973,7 @@ converse.plugins.add('converse-muc', {
// XXX: very naive duplication checking // XXX: very naive duplication checking
return; return;
} }
this.messages.create(data); this.createMessage(data);
} }
}); });
}, },
...@@ -2649,3 +2662,5 @@ converse.plugins.add('converse-muc', { ...@@ -2649,3 +2662,5 @@ converse.plugins.add('converse-muc', {
/************************ END API ************************/ /************************ END API ************************/
} }
}); });
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
// Opens a new chatroom // Opens a new chatroom
const model = await _converse.api.controlbox.open('controlbox'); const model = await _converse.api.controlbox.open('controlbox');
await u.waitUntil(() => model.get('connected')); await u.waitUntil(() => model.get('connected'));
utils.openControlBox(); await utils.openControlBox(_converse);
const view = await _converse.chatboxviews.get('controlbox'); const view = await _converse.chatboxviews.get('controlbox');
const roomspanel = view.roomspanel; const roomspanel = view.roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
......
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