diff --git a/CHANGES.md b/CHANGES.md
index 8c9dcc8f5..948bf7505 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,7 @@
- #1426: Don't fetch member list if not affiliated
- #2423: Could not find dependency "converse-controlbox" for plugin "converse-muc"
- #2647: Singleton mode doesn't work
+- #2683: Show error messages that don't refer to specific chat messages
- #2704: Send button doesn't work in a multi-user chat
- #2715: Singleton + fullscreen mode styling issue
- #2718: Message is not displayed if it contains an invalid URL
diff --git a/src/headless/plugins/chat/model.js b/src/headless/plugins/chat/model.js
index 5d9d726d8..102e5bb90 100644
--- a/src/headless/plugins/chat/model.js
+++ b/src/headless/plugins/chat/model.js
@@ -508,9 +508,9 @@ const ChatBox = ModelWithContact.extend({
*/
shouldShowErrorMessage (attrs) {
const msg = this.getMessageReferencedByError(attrs);
- if (!msg && !attrs.body) {
+ if (!msg && attrs.chat_state) {
// If the error refers to a message not included in our store,
- // and it doesn't have a
tag, we assume that this was a
+ // and it has a chat state tag, we assume that this was a
// CSI message (which we don't store).
// See https://github.com/conversejs/converse.js/issues/1317
return;
diff --git a/src/plugins/chatview/tests/messages.js b/src/plugins/chatview/tests/messages.js
index 5bff3d2be..e4904acd3 100644
--- a/src/plugins/chatview/tests/messages.js
+++ b/src/plugins/chatview/tests/messages.js
@@ -1006,13 +1006,15 @@ describe("A Chat Message", function () {
}));
});
-
describe("and for which then an error message is received from the server", function () {
it("will have the error message displayed after itself",
- mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
+ mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 1);
+ const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
+
+ await mock.openChatBoxFor(_converse, sender_jid);
// TODO: what could still be done for error
// messages... if the element has type
@@ -1029,8 +1031,6 @@ describe("A Chat Message", function () {
*
*/
const error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout';
- const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
- await _converse.api.chats.open(sender_jid)
let msg_text = 'This message will not be sent, due to an error';
const view = _converse.chatboxviews.get(sender_jid);
const message = await view.model.sendMessage({'body': msg_text});
@@ -1090,7 +1090,7 @@ describe("A Chat Message", function () {
stanza = $msg({
'to': _converse.connection.jid,
'type':'error',
- 'id': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104',
+ 'id': second_message.get('id'),
'from': sender_jid
})
.c('error', {'type': 'cancel'})
@@ -1134,36 +1134,86 @@ describe("A Chat Message", function () {
// See #1317
// https://github.com/conversejs/converse.js/issues/1317
- await mock.waitForRoster(_converse, 'current');
- await mock.openControlBox(_converse);
-
- const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@montague.lit';
+ await mock.waitForRoster(_converse, 'current', 1);
+ const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
-
- const messages = _converse.connection.sent_stanzas.filter(s => s.nodeName === 'message');
- expect(messages.length).toBe(1);
- expect(Strophe.serialize(messages[0])).toBe(
- ``+
- ``+
- ``+
- ``+
- ``);
-
- const stanza = $msg({
- 'from': contact_jid,
- 'type': 'error',
- 'id': messages[0].getAttribute('id')
- }).c('error', {'type': 'cancel', 'code': '503'})
- .c('service-unavailable', { 'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas' }).up()
- .c('text', { 'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas' })
- .t('User session not found')
- _converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get(contact_jid);
- const msg_text = 'This message will show!';
- await view.model.sendMessage({'body': msg_text});
- await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
- expect(view.querySelectorAll('.chat-error').length).toEqual(0);
+
+ const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
+ textarea.value = 'hello world'
+ const enter_event = {
+ 'target': textarea,
+ 'preventDefault': function preventDefault () {},
+ 'stopPropagation': function stopPropagation () {},
+ 'keyCode': 13 // Enter
+ }
+ const message_form = view.querySelector('converse-message-form');
+ message_form.onKeyDown(enter_event);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+
+ const msg = $msg({
+ from: contact_jid,
+ to: _converse.connection.jid,
+ type: 'chat',
+ id: u.getUniqueId()
+ }).c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
+ await _converse.handleMessageStanza(msg);
+
+ _converse.connection._dataRecv(mock.createRequest(u.toStanza(`
+
+
+
+
+
+
+ User session not found
+
+ `)));
+ return new Promise(resolve => setTimeout(() => {
+ expect(view.querySelector('.chat-msg__error').textContent).toBe('');
+ resolve();
+ }, 500));
}));
+
+ it("will have the error displayed below it",
+ mock.initConverse([], {}, async function (_converse) {
+
+ await mock.waitForRoster(_converse, 'current', 1);
+ const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
+ await mock.openChatBoxFor(_converse, contact_jid);
+ const view = _converse.chatboxviews.get(contact_jid);
+
+ const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
+ textarea.value = 'hello world'
+ const enter_event = {
+ 'target': textarea,
+ 'preventDefault': function preventDefault () {},
+ 'stopPropagation': function stopPropagation () {},
+ 'keyCode': 13 // Enter
+ }
+ const message_form = view.querySelector('converse-message-form');
+ message_form.onKeyDown(enter_event);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+
+ // Normally "modify" errors need to have their id set to the
+ // message that couldn't be sent. Not doing that here on purpose to
+ // check the case where it's not done.
+ // See issue #2683
+ const err_txt = `Your message to ${contact_jid} was not end-to-end encrypted. For security reasons, using one of the following E2EE schemes is *REQUIRED* for conversations on this server: pgp, omemo`;
+ const error = u.toStanza(`
+
+
+
+ ${err_txt}
+
+
+ `);
+ _converse.connection._dataRecv(mock.createRequest(error));
+
+ expect(await u.waitUntil(() => view.querySelector('.chat-error')?.textContent?.trim())).toBe(err_txt);
+ expect(view.model.messages.length).toBe(2);
+ }));
+
});
it("will cause the chat area to be scrolled down only if it was at the bottom originally",
diff --git a/src/plugins/profile/templates/profile_modal.js b/src/plugins/profile/templates/profile_modal.js
index 42bc13d87..2021cabf8 100644
--- a/src/plugins/profile/templates/profile_modal.js
+++ b/src/plugins/profile/templates/profile_modal.js
@@ -84,7 +84,7 @@ export default (o) => {
- ${ _converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? omemo_page(o) : '' }
+ ${ _converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? omemo_page() : '' }