Commit b5eea12d authored by JC Brand's avatar JC Brand

Refactor so that message attributes are parsed early

It's better to parse an incoming message stanza early, than to have
all kinds of methods throughout the codebase that does querySelector
etc.

Firstly, it allows us to catch and report errors and malicious stanzas early on.
It also simplifies programming because you don't need to try and
remember how to properly parse a stanza, all the work is done upfront
for you.
parent 27008aff
......@@ -37,7 +37,7 @@ describe("The nickname autocomplete feature", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('Hello world').tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
......
......@@ -6,7 +6,7 @@ describe("A headlines box", function () {
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
const { u, $msg} = converse.env;
const { $msg } = converse.env;
/* XMPP spam message:
*
* <message xmlns="jabber:client"
......@@ -17,7 +17,6 @@ describe("A headlines box", function () {
* <body>SORRY FOR THIS ADVERT</body
* </message
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'xmlns': 'jabber:client',
'to': 'romeo@montague.lit',
......@@ -27,10 +26,7 @@ describe("A headlines box", function () {
.c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
.c('body').t('SORRY FOR THIS ADVERT');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(false)).toBeTruthy();
expect(_converse.api.headlines.get().length === 0);
u.isHeadlineMessage.restore();
done();
}));
......@@ -51,7 +47,6 @@ describe("A headlines box", function () {
* </x>
* </message>
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
......@@ -65,9 +60,6 @@ describe("A headlines box", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.chatboxviews.keys().includes('notify.example.com'));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
const view = _converse.chatboxviews.get('notify.example.com');
expect(view.model.get('show_avatar')).toBeFalsy();
expect(view.el.querySelector('img.avatar')).toBe(null);
......@@ -155,9 +147,8 @@ describe("A headlines box", function () {
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
const { u, $msg, _ } = converse.env;
const { $msg, _ } = converse.env;
_converse.allow_non_roster_messaging = false;
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'andre5114@jabber.snc.ru/Spark',
......@@ -168,9 +159,6 @@ describe("A headlines box", function () {
.c('body').t('Здравствуйте друзья');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
done();
}));
});
......@@ -294,7 +294,7 @@ describe("Message Archive Management", function () {
</message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough();
spyOn(view.model, 'updateMessage').and.callThrough();
view.model.queueMessage(stanza);
view.model.handleMAMResult({ 'messages': [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
......@@ -338,7 +338,7 @@ describe("Message Archive Management", function () {
</result>
</message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough();
view.model.queueMessage(stanza);
view.model.handleMAMResult({ 'messages': [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
......@@ -368,7 +368,7 @@ describe("Message Archive Management", function () {
</forwarded>
</result>
</message>`);
view.model.queueMessage(stanza);
view.model.handleMAMResult({ 'messages': [stanza] });
await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
......@@ -388,7 +388,7 @@ describe("Message Archive Management", function () {
</message>`);
spyOn(view.model, 'getDuplicateMessage').and.callThrough();
view.model.queueMessage(stanza);
view.model.handleMAMResult({ 'messages': [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
......
......@@ -512,9 +512,8 @@ describe("A Chat Message", function () {
// Ideally we wouldn't have to filter out headline
// messages, but Prosody gives them the wrong 'type' :(
sinon.spy(converse.env.log, 'info');
spyOn(converse.env.log, 'info');
sinon.spy(_converse.api.chatboxes, 'get');
sinon.spy(u, 'isHeadlineMessage');
const msg = $msg({
from: 'montague.lit',
to: _converse.bare_jid,
......@@ -522,16 +521,12 @@ describe("A Chat Message", function () {
id: u.getUniqueId()
}).c('body').t("This headline message will not be shown").tree();
await _converse.handleMessageStanza(msg);
expect(converse.env.log.info.calledWith(
"handleMessageStanza: Ignoring incoming headline message from JID: montague.lit"
)).toBeTruthy();
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
expect(converse.env.log.info).toHaveBeenCalledWith(
"handleMessageStanza: Ignoring incoming server message from JID: montague.lit"
);
expect(_converse.api.chatboxes.get.called).toBeFalsy();
// Remove sinon spies
converse.env.log.info.restore();
_converse.api.chatboxes.get.restore();
u.isHeadlineMessage.restore();
done();
}));
......@@ -1561,7 +1556,7 @@ describe("A Chat Message", function () {
* </message>
*/
const error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout';
const sender_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let fullname = _converse.xmppstatus.get('fullname'); // eslint-disable-line no-unused-vars
fullname = _.isEmpty(fullname) ? _converse.bare_jid: fullname;
await _converse.api.chats.open(sender_jid)
......@@ -1757,7 +1752,7 @@ describe("A Chat Message", function () {
await mock.waitForRoster(_converse, 'current');
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
// Send a message from a different resource
spyOn(converse.env.log, 'info');
spyOn(converse.env.log, 'error');
spyOn(_converse.api.chatboxes, 'create').and.callThrough();
_converse.filter_by_resource = true;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
......@@ -1770,8 +1765,8 @@ describe("A Chat Message", function () {
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.handleMessageStanza(msg);
expect(converse.env.log.info).toHaveBeenCalledWith(
"handleMessageStanza: Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
expect(converse.env.log.error.calls.all().pop().args[0]).toBe(
"Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
);
expect(_converse.api.chatboxes.create).not.toHaveBeenCalled();
_converse.filter_by_resource = false;
......
......@@ -156,7 +156,7 @@ describe("The Minimized Chats Widget", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
view.model.queueMessage(msg);
view.model.handleMessageStanza(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');
......
......@@ -518,8 +518,7 @@ describe("Groupchats", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
await new Promise(resolve => view.model.once('change:subject', resolve));
const head_desc = await u.waitUntil(() => view.el.querySelector('.chat-head__desc'));
const head_desc = await u.waitUntil(() => view.el.querySelector('.chat-head__desc'), 1000);
expect(head_desc?.textContent.trim()).toBe(text);
stanza = u.toStanza(
......@@ -701,7 +700,7 @@ describe("Groupchats", function () {
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
spyOn(view.model, 'clearMessages').and.callThrough();
await view.model.close();
......@@ -732,7 +731,7 @@ describe("Groupchats", function () {
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-msg__text a'));
view.el.querySelector('.chat-msg__text a').click();
await u.waitUntil(() => _converse.chatboxes.length === 3)
......@@ -1269,7 +1268,7 @@ describe("Groupchats", function () {
'type': 'groupchat'
}).c('body').t('Some message').tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view.content).pop());
let stanza = u.toStanza(
......@@ -1318,7 +1317,7 @@ describe("Groupchats", function () {
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view.content).pop());
expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Dyon van de Wege')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired');
......@@ -1330,7 +1329,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(sizzle('.chat-msg__author:last', view.el).pop().textContent.includes('**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent.trim()).toBe('is as well');
......@@ -2016,7 +2015,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await view.model.handleMessageStanza(message.nodeTree);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.content.querySelector('.chat-msg__text').textContent.trim()).toBe(text);
......@@ -2061,7 +2060,7 @@ describe("Groupchats", function () {
by="lounge@montague.lit"/>
<origin-id xmlns="urn:xmpp:sid:0" id="${view.model.messages.at(0).get('origin_id')}"/>
</message>`);
await view.model.queueMessage(stanza);
await view.model.handleMessageStanza(stanza);
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
expect(sizzle('.chat-msg__text:last').pop().textContent.trim()).toBe(text);
expect(view.model.messages.length).toBe(1);
......@@ -2083,7 +2082,7 @@ describe("Groupchats", function () {
const promises = [];
for (let i=0; i<20; i++) {
promises.push(
view.model.queueMessage(
view.model.handleMessageStanza(
$msg({
from: 'lounge@montague.lit/someone',
to: 'romeo@montague.lit.com',
......@@ -2096,7 +2095,7 @@ describe("Groupchats", function () {
// Give enough time for `markScrolled` to have been called
setTimeout(async () => {
view.content.scrollTop = 0;
await view.model.queueMessage(
await view.model.handleMessageStanza(
$msg({
from: 'lounge@montague.lit/someone',
to: 'romeo@montague.lit.com',
......@@ -4863,8 +4862,7 @@ describe("Groupchats", function () {
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.queueMessage($msg({
await view.model.handleMessageStanza($msg({
from: muc_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
......@@ -4875,7 +4873,7 @@ describe("Groupchats", function () {
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.el.querySelector('.msgs-indicator').textContent.trim()).toBe('1');
await view.model.queueMessage($msg({
await view.model.handleMessageStanza($msg({
'from': muc_jid+'/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
......@@ -5027,7 +5025,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() === 'newguy and nomorenicks are typing');
// <composing> state for a different occupant
......@@ -5037,7 +5035,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and majortom are typing');
// <composing> state for a different occupant
......@@ -5047,7 +5045,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and others are typing');
// Check that new messages appear under the chat state notifications
......@@ -5057,7 +5055,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('hello world').tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
const messages = view.el.querySelectorAll('.message');
......@@ -5146,7 +5144,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent);
expect(view.el.querySelector('.chat-content__notifications').textContent.trim()).toBe('newguy is typing');
......@@ -5157,7 +5155,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() == 'newguy and nomorenicks are typing');
......@@ -5168,7 +5166,7 @@ describe("Groupchats", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
await view.model.queueMessage(msg);
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() == 'nomorenicks is typing\nnewguy has stopped typing');
done();
}));
......
This diff is collapsed.
......@@ -95,10 +95,7 @@ describe("Notifications", function () {
await u.waitUntil(() => _converse.chatboxviews.keys().length);
const view = _converse.chatboxviews.get('notify.example.com');
await new Promise(resolve => view.once('messageInserted', resolve));
expect(
_.includes(_converse.chatboxviews.keys(),
'notify.example.com')
).toBeTruthy();
expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
......@@ -175,7 +172,7 @@ describe("Notifications", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await view.model.handleMessageStanza(message.nodeTree);
await u.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled();
......@@ -186,7 +183,7 @@ describe("Notifications", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await view.model.handleMessageStanza(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
......@@ -197,7 +194,7 @@ describe("Notifications", function () {
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await view.model.handleMessageStanza(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
done();
......
......@@ -19,7 +19,7 @@ async function sendAndThenRetractMessage (_converse, view) {
by="lounge@montague.lit"/>
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.queueMessage(reflection_stanza);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
const retract_button = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .chat-msg__action-retract'));
......@@ -52,7 +52,7 @@ describe("Message Retractions", function () {
</message>
`);
const view = _converse.api.chatviews.get(muc_jid);
await view.model.queueMessage(received_stanza);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
......@@ -394,7 +394,7 @@ describe("Message Retractions", function () {
</message>
`);
const view = _converse.api.chatviews.get(muc_jid);
await view.model.queueMessage(received_stanza);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
......@@ -440,7 +440,7 @@ describe("Message Retractions", function () {
<stanza-id xmlns='urn:xmpp:sid:0' id='stanza-id-1' by='${muc_jid}'/>
</message>
`);
await view.model.queueMessage(received_stanza);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.model.messages.length === 1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
......@@ -498,7 +498,7 @@ describe("Message Retractions", function () {
</moderated>
</apply-to>
</message>`);
await view.model.queueMessage(retraction);
await view.model.handleMessageStanza(retraction);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.model.messages.at(0).get('moderation_reason')).toBe(reason);
......@@ -524,7 +524,7 @@ describe("Message Retractions", function () {
<stanza-id xmlns='urn:xmpp:sid:0' id='stanza-id-1' by='${muc_jid}'/>
</message>
`);
await view.model.queueMessage(received_stanza);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelector('.chat-msg__content'));
expect(view.el.querySelector('.chat-msg__content .chat-msg__action-retract')).toBe(null);
const result = await view.model.canModerateMessages();
......@@ -551,7 +551,7 @@ describe("Message Retractions", function () {
<stanza-id xmlns='urn:xmpp:sid:0' id='stanza-id-1' by='${muc_jid}'/>
</message>
`);
await view.model.queueMessage(received_stanza);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.model.messages.length === 1);
expect(view.model.messages.length).toBe(1);
......@@ -579,7 +579,7 @@ describe("Message Retractions", function () {
</moderated>
</apply-to>
</message>`);
await view.model.queueMessage(retraction);
await view.model.handleMessageStanza(retraction);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(1);
......@@ -778,7 +778,7 @@ describe("Message Retractions", function () {
by="lounge@montague.lit"/>
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.queueMessage(reflection_stanza);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('editable')).toBe(true);
......@@ -794,7 +794,7 @@ describe("Message Retractions", function () {
</moderated>
</apply-to>
</message>`);
await view.model.queueMessage(retraction);
await view.model.handleMessageStanza(retraction);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.model.messages.at(0).get('moderation_reason')).toBe(reason);
......@@ -830,7 +830,7 @@ describe("Message Retractions", function () {
by="lounge@montague.lit"/>
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.queueMessage(reflection_stanza);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('editable')).toBe(true);
......@@ -879,7 +879,7 @@ describe("Message Retractions", function () {
</moderated>
</apply-to>
</message>`);
await view.model.queueMessage(retraction);
await view.model.handleMessageStanza(retraction);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.model.messages.at(0).get('moderation_reason')).toBe(undefined);
......
......@@ -289,7 +289,7 @@ describe("A groupchat shown in the groupchats list", function () {
const view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.queueMessage(
await view.model.handleMessageStanza(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
......@@ -303,7 +303,7 @@ describe("A groupchat shown in the groupchats list", function () {
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated
await view.model.queueMessage(
await view.model.handleMessageStanza(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
......@@ -316,7 +316,7 @@ describe("A groupchat shown in the groupchats list", function () {
expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.queueMessage(
await view.model.handleMessageStanza(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
......
......@@ -5,6 +5,7 @@
*/
import { converse } from "@converse/headless/converse-core";
import log from "@converse/headless/log";
import st from "@converse/headless/utils/stanza";
const { Strophe, sizzle } = converse.env;
const u = converse.env.utils;
......@@ -79,7 +80,7 @@ converse.plugins.add('converse-notification', {
return false;
} else if (message.getAttribute('type') === 'groupchat') {
return _converse.shouldNotifyOfGroupMessage(message);
} else if (u.isHeadlineMessage(_converse, message)) {
} else if (st.isHeadline(message)) {
// We want to show notifications for headline messages.
return _converse.isMessageToHiddenChat(message);
}
......
This diff is collapsed.
......@@ -7,8 +7,6 @@ import { isString } from "lodash";
import { converse } from "@converse/headless/converse-core";
import st from "./utils/stanza";
const u = converse.env.utils;
converse.plugins.add('converse-headlines', {
/* Plugin dependencies are other plugins which might be
......@@ -83,7 +81,7 @@ converse.plugins.add('converse-headlines', {
async function onHeadlineMessage (stanza) {
// Handler method for all incoming messages of type "headline".
if (u.isHeadlineMessage(_converse, stanza)) {
if (st.isHeadline(stanza) || st.isServerMessage(stanza)) {
const from_jid = stanza.getAttribute('from');
if (from_jid.includes('@') &&
!_converse.roster.get(from_jid) &&
......@@ -100,7 +98,7 @@ converse.plugins.add('converse-headlines', {
'type': _converse.HEADLINES_TYPE,
'from': from_jid
});
const attrs = await st.parseMessage(stanza, stanza, chatbox, _converse);
const attrs = await st.parseMessage(stanza, _converse);
await chatbox.createMessage(attrs);
api.trigger('message', {'chatbox': chatbox, 'stanza': stanza});
}
......@@ -109,10 +107,7 @@ converse.plugins.add('converse-headlines', {
/************************ BEGIN Event Handlers ************************/
function registerHeadlineHandler () {
_converse.connection.addHandler(message => {
onHeadlineMessage(message);
return true
}, null, 'message');
_converse.connection.addHandler(message => (onHeadlineMessage(message) || true), null, 'message');
}
api.listen.on('connected', registerHeadlineHandler);
api.listen.on('reconnected', registerHeadlineHandler);
......
This diff is collapsed.
......@@ -608,6 +608,26 @@ converse.plugins.add('converse-muc', {
}
},
async handleErrormessageStanza (stanza) {
if (await this.shouldShowErrorMessage(stanza)) {
this.createMessage(await st.parseMUCMessage(stanza, this, _converse));
}
},
async handleMessageStanza (stanza) {
if (st.isArchived(stanza)) {
// MAM messages are handled in converse-mam.
// We shouldn't get MAM messages here because
// they shouldn't have a `type` attribute.
return log.warn(`Received a MAM message with type "groupchat"`);
}
api.trigger('message', {'stanza': stanza});
this.createInfoMessages(stanza);
this.fetchFeaturesIfConfigurationChanged(stanza);
const attrs = await st.parseMUCMessage(stanza, this, _converse);
return attrs && this.queueMessage(attrs);
},
registerHandlers () {
// Register presence and message handlers for this groupchat
const room_jid = this.get('jid');
......@@ -618,17 +638,9 @@ converse.plugins.add('converse-muc', {
{'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
);
this.message_handler = _converse.connection.addHandler(stanza => {
if (sizzle(`message > result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop()) {
// MAM messages are handled in converse-mam.
// We shouldn't get MAM messages here because
// they shouldn't have a `type` attribute.
log.warn(`received a mam message with type "chat".`);
return true;
}
this.queueMessage(stanza);
return true;
}, null, 'message', 'groupchat', null, room_jid,
this.message_handler = _converse.connection.addHandler(
stanza => (!!this.handleMessageStanza(stanza) || true),
null, 'message', 'groupchat', null, room_jid,
{'matchBareFromJid': true}
);
......@@ -1650,7 +1662,7 @@ converse.plugins.add('converse-muc', {
* @private
* @method _converse.ChatRoom#handleSubjectChange
* @param { object } attrs - Attributes representing a received
* message, as returned by {@link st.parseMessage}
* message, as returned by {@link st.parseMUCMessage}
*/
async handleSubjectChange (attrs) {
if (isString(attrs.subject) && !attrs.thread && !attrs.message) {
......@@ -1703,8 +1715,7 @@ converse.plugins.add('converse-muc', {
* @param { Object } attrs - The message attributes
*/
ignorableCSN (attrs) {
const is_csn = u.isOnlyChatStateNotification(attrs);
return is_csn && (attrs.is_delayed || this.isOwnMessage(attrs));
return attrs.chat_state && !attrs.body && (attrs.is_delayed || this.isOwnMessage(attrs));
},
......@@ -1729,21 +1740,17 @@ converse.plugins.add('converse-muc', {
},
getUpdatedMessageAttributes (message, stanza) {
getUpdatedMessageAttributes (message, attrs) {
// Overridden in converse-muc and converse-mam
const attrs = _converse.ChatBox.prototype.getUpdatedMessageAttributes.call(this, message, stanza);
if (this.isOwnMessage(message)) {
const stanza_id = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop();
const by_jid = stanza_id ? stanza_id.getAttribute('by') : undefined;
if (by_jid) {
const key = `stanza_id ${by_jid}`;
attrs[key] = stanza_id.getAttribute('id');
}
const new_attrs = _converse.ChatBox.prototype.getUpdatedMessageAttributes.call(this, message, attrs);
if (this.isOwnMessage(attrs)) {
const stanza_id_keys = Object.keys(attrs).filter(k => k.startsWith('stanza_id'));
Object.assign(new_attrs, pick(attrs, stanza_id_keys));
if (!message.get('received')) {
attrs.received = (new Date()).toISOString();
new_attrs.received = (new Date()).toISOString();
}
}
return attrs;
return new_attrs;
},
/**
......@@ -1808,7 +1815,7 @@ converse.plugins.add('converse-muc', {
* @private
* @method _converse.ChatRoom#findDanglingModeration
* @param { object } attrs - Attributes representing a received
* message, as returned by {@link st.parseMessage}
* message, as returned by {@link st.parseMUCMessage}
* @returns { _converse.ChatRoomMessage }
*/
findDanglingModeration (attrs) {
......@@ -1839,7 +1846,7 @@ converse.plugins.add('converse-muc', {
* @private
* @method _converse.ChatRoom#handleModeration
* @param { object } attrs - Attributes representing a received
* message, as returned by {@link st.parseMessage}
* message, as returned by {@link st.parseMUCMessage}
* @returns { Boolean } Returns `true` or `false` depending on
* whether a message was moderated or not.
*/
......@@ -1954,47 +1961,25 @@ converse.plugins.add('converse-muc', {
* should be called.
* @private
* @method _converse.ChatRoom#onMessage
* @param { XMLElement } stanza - The message stanza.
* @param { MessageAttributes } attrs - The message attributes
*/
async onMessage (stanza) {
if (sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length) {
return log.warn('onMessage: Ignoring unencapsulated forwarded groupchat message');
}
if (u.isCarbonMessage(stanza)) {
return log.warn(
'onMessage: Ignoring XEP-0280 "groupchat" message carbon, '+
'according to the XEP groupchat messages SHOULD NOT be carbon copied'
);
}
const original_stanza = stanza;
if (u.isMAMMessage(stanza)) {
if (original_stanza.getAttribute('from') === this.get('jid')) {
const selector = `[xmlns="${Strophe.NS.MAM}"] > forwarded[xmlns="${Strophe.NS.FORWARD}"] > message`;
stanza = sizzle(selector, stanza).pop();
} else {
return log.warn(`onMessage: Ignoring alleged MAM groupchat message from ${stanza.getAttribute('from')}`);
}
async onMessage (attrs) {
if (u.isErrorObject(attrs)) {
attrs.stanza && log.error(attrs.stanza);
return log.error(attrs.message);
}
await this.createInfoMessages(stanza);
this.fetchFeaturesIfConfigurationChanged(stanza);
const attrs = await st.parseMessage(stanza, original_stanza, this, _converse);
// TODO: move to OMEMO
attrs = attrs.encrypted ? await this.decrypt(attrs) : attrs;
const message = this.getDuplicateMessage(attrs);
if (message) {
this.updateMessage(message, original_stanza);
}
if (message ||
st.isReceipt(stanza) ||
st.isChatMarker(stanza) ||
this.ignorableCSN(attrs)) {
return api.trigger('message', {'stanza': original_stanza});
return this.updateMessage(message, attrs);
} else if (attrs.is_receipt_request || attrs.is_marker || this.ignorableCSN(attrs)) {
return;
}
if (await this.handleRetraction(attrs) ||
await this.handleModeration(attrs) ||
await this.handleSubjectChange(attrs)) {
this.removeNotification(attrs.nick, ['composing', 'paused']);
return api.trigger('message', {'stanza': original_stanza});
return this.removeNotification(attrs.nick, ['composing', 'paused']);
}
this.setEditable(attrs, attrs.time);
......@@ -2006,7 +1991,6 @@ converse.plugins.add('converse-muc', {
this.removeNotification(attrs.nick, ['composing', 'paused']);
this.incrementUnreadMsgCounter(msg);
}
api.trigger('message', {'stanza': original_stanza, 'chatbox': this});
},
handleModifyError(pres) {
......
......@@ -42,16 +42,6 @@ u.toStanza = function (string) {
return node.firstElementChild;
}
u.isMAMMessage = function (stanza) {
return sizzle(`message > result[xmlns="${Strophe.NS.MAM}"]`, stanza).length > 0;
}
u.isCarbonMessage = function (stanza) {
const xmlns = Strophe.NS.CARBONS;
return sizzle(`message > received[xmlns="${xmlns}"]`, stanza).length > 0 ||
sizzle(`message > sent[xmlns="${xmlns}"]`, stanza).length > 0;
}
u.getLongestSubstring = function (string, candidates) {
function reducer (accumulator, current_value) {
if (string.startsWith(current_value)) {
......@@ -142,6 +132,7 @@ u.isEmptyMessage = function (attrs) {
!attrs['message'];
};
//TODO: Remove
u.isOnlyChatStateNotification = function (msg) {
if (msg instanceof Element) {
// See XEP-0085 Chat State Notification
......@@ -174,25 +165,6 @@ u.isChatRoom = function (model) {
return model && (model.get('type') === 'chatroom');
}
u.isHeadlineMessage = function (_converse, message) {
const from_jid = message.getAttribute('from');
if (message.getAttribute('type') === 'headline') {
return true;
}
const chatbox = _converse.chatboxes.get(Strophe.getBareJidFromJid(from_jid));
if (u.isChatRoom(chatbox)) {
return false;
}
if (message.getAttribute('type') !== 'error' && from_jid && !from_jid.includes('@')) {
// Some servers (I'm looking at you Prosody) don't set the message
// type to "headline" when sending server messages. For now we
// check if an @ signal is included, and if not, we assume it's
// a headline message.
return true;
}
return false;
};
u.isErrorObject = function (o) {
return o instanceof Error;
}
......
This diff is collapsed.
......@@ -34,7 +34,7 @@
websocket_url: 'ws://chat.example.org:5380/xmpp-websocket',
// bosh_service_url: 'http://chat.example.org:5280/http-bind',
muc_show_logs_before_join: true,
whitelisted_plugins: ['converse-debug'],
whitelisted_plugins: ['converse-debug', 'converse-batched-probe'],
});
</script>
</html>
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