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