diff --git a/CHANGES.md b/CHANGES.md index f703e4056..650007d0f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ - Bugfix: MUC commands were being ignored - UI: Always show the OMEMO lock icon (grayed out if not available). +- #1353 Message Delivery Receipts not working because of the message "type" attribute - #1374 Can't load embedded chat when changing `view_mode` between page reloads - #1376 Fixed some alignment issues in the sidebar - #1378 Message Delivery Receipts were being sent for carbons and own messages diff --git a/dist/converse.js b/dist/converse.js index 0cfc3b0ea..63c882051 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -54367,7 +54367,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins break; case 'help': - this.showHelpMessages([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Remove messages')}`, `/deop: ${__('Change user role to participant')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register a nickname for this room")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`]); + this.showHelpMessages([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Remove messages')}`, `/deop: ${__('Change user role to participant')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register a nickname for this groupchat")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`]); break; case 'kick': @@ -61415,7 +61415,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha 'send_chat_state_notifications': true }); - _converse.api.promises.add(['chatBoxesFetched', 'chatBoxesInitialized', 'privateChatsAutoJoined']); + _converse.api.promises.add(['chatBoxesFetched', 'ehatBoxesInitialized', 'privateChatsAutoJoined']); function openChat(jid) { if (!utils.isValidJID(jid)) { @@ -62086,6 +62086,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha return true; }, null, 'message', 'chat'); + _converse.connection.addHandler(stanza => { + // Message receipts are usually without the `type` attribute. See #1353 + if (!_.isNull(stanza.getAttribute('type'))) { + // TODO: currently Strophe has no way to register a handler + // for stanzas without a `type` attribute. + // We could update it to accept null to mean no attribute, + // but that would be a backward-incompatible chnge + return true; // Gets handled above. + } + + this.onMessage(stanza); + return true; + }, Strophe.NS.RECEIPTS, 'message'); + _converse.connection.addHandler(stanza => { this.onErrorMessage(stanza); return true; @@ -62202,7 +62216,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha // XXX: Ideally we wouldn't have to check for headline // messages, but Prosody sends headline messages with the // wrong type ('chat'), so we need to filter them out here. - _converse.log(`onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ${stanza.getAttribute('from')}`, Strophe.LogLevel.INFO); + _converse.log(`onMessage: Ignoring incoming headline message from JID: ${stanza.getAttribute('from')}`, Strophe.LogLevel.INFO); return true; } @@ -69597,7 +69611,7 @@ u.isOnlyChatStateNotification = function (attrs) { }; u.isHeadlineMessage = function (_converse, message) { - var from_jid = message.getAttribute('from'); + const from_jid = message.getAttribute('from'); if (message.getAttribute('type') === 'headline') { return true; @@ -69605,7 +69619,7 @@ u.isHeadlineMessage = function (_converse, message) { const chatbox = _converse.chatboxes.get(strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].getBareJidFromJid(from_jid)); - if (chatbox && chatbox.get('type') === 'chatroom') { + if (chatbox && chatbox.get('type') === _converse.CHATROOMS_TYPE) { return false; } diff --git a/spec/messages.js b/spec/messages.js index 489044383..87521a417 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -7,14 +7,7 @@ ], factory); } (this, function ($, jasmine, mock, test_utils) { "use strict"; - const _ = converse.env._; - const sizzle = converse.env.sizzle; - const $iq = converse.env.$iq; - const $msg = converse.env.$msg; - const $pres = converse.env.$pres; - const Strophe = converse.env.Strophe; - const Promise = converse.env.Promise; - const moment = converse.env.moment; + const { Backbone, Promise, Strophe, $iq, $msg, $pres, b64_sha1, moment, sizzle, _ } = converse.env; const u = converse.env.utils; @@ -490,7 +483,7 @@ }).c('body').t("This headline message will not be shown").tree(); _converse.chatboxes.onMessage(msg); expect(_converse.log.calledWith( - "onMessage: Ignoring incoming headline message sent with type 'chat' from JID: localhost", + "onMessage: Ignoring incoming headline message from JID: localhost", Strophe.LogLevel.INFO )).toBeTruthy(); expect(u.isHeadlineMessage.called).toBeTruthy(); @@ -1304,16 +1297,39 @@ const chatbox = _converse.chatboxes.get(contact_jid); expect(chatbox).toBeDefined(); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - const msg_obj = chatbox.messages.models[0]; - const msg_id = msg_obj.get('msgid'); - const msg = $msg({ + let msg_obj = chatbox.messages.models[0]; + let msg_id = msg_obj.get('msgid'); + let msg = $msg({ 'from': contact_jid, 'to': _converse.connection.jid, 'id': u.getUniqueId(), }).c('received', {'id': msg_id, xmlns: Strophe.NS.RECEIPTS}).up().tree(); - _converse.chatboxes.onMessage(msg); + _converse.connection._dataRecv(test_utils.createRequest(msg)); await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve)); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1); + + // Also handle receipts with type 'chat'. See #1353 + spyOn(_converse.chatboxes, 'onMessage').and.callThrough(); + textarea.value = 'Another message'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); + + msg_obj = chatbox.messages.models[1]; + msg_id = msg_obj.get('msgid'); + msg = $msg({ + 'from': contact_jid, + 'type': 'chat', + 'to': _converse.connection.jid, + 'id': u.getUniqueId(), + }).c('received', {'id': msg_id, xmlns: Strophe.NS.RECEIPTS}).up().tree(); + _converse.connection._dataRecv(test_utils.createRequest(msg)); + await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve)); + expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(2); + expect(_converse.chatboxes.onMessage.calls.count()).toBe(1); done(); })); diff --git a/src/headless/converse-chatboxes.js b/src/headless/converse-chatboxes.js index dae1149bd..dbe74b4b3 100644 --- a/src/headless/converse-chatboxes.js +++ b/src/headless/converse-chatboxes.js @@ -40,7 +40,7 @@ converse.plugins.add('converse-chatboxes', { }); _converse.api.promises.add([ 'chatBoxesFetched', - 'chatBoxesInitialized', + 'ehatBoxesInitialized', 'privateChatsAutoJoined' ]); @@ -635,6 +635,20 @@ converse.plugins.add('converse-chatboxes', { this.onMessage(stanza); return true; }, null, 'message', 'chat'); + + _converse.connection.addHandler(stanza => { + // Message receipts are usually without the `type` attribute. See #1353 + if (!_.isNull(stanza.getAttribute('type'))) { + // TODO: currently Strophe has no way to register a handler + // for stanzas without a `type` attribute. + // We could update it to accept null to mean no attribute, + // but that would be a backward-incompatible chnge + return true; // Gets handled above. + } + this.onMessage(stanza); + return true; + }, Strophe.NS.RECEIPTS, 'message'); + _converse.connection.addHandler(stanza => { this.onErrorMessage(stanza); return true; @@ -740,7 +754,7 @@ converse.plugins.add('converse-chatboxes', { // messages, but Prosody sends headline messages with the // wrong type ('chat'), so we need to filter them out here. _converse.log( - `onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ${stanza.getAttribute('from')}`, + `onMessage: Ignoring incoming headline message from JID: ${stanza.getAttribute('from')}`, Strophe.LogLevel.INFO ); return true; diff --git a/src/headless/utils/core.js b/src/headless/utils/core.js index 931f67714..bfbe89b34 100644 --- a/src/headless/utils/core.js +++ b/src/headless/utils/core.js @@ -88,12 +88,12 @@ u.isOnlyChatStateNotification = function (attrs) { }; u.isHeadlineMessage = function (_converse, message) { - var from_jid = message.getAttribute('from'); + const from_jid = message.getAttribute('from'); if (message.getAttribute('type') === 'headline') { return true; } const chatbox = _converse.chatboxes.get(Strophe.getBareJidFromJid(from_jid)); - if (chatbox && chatbox.get('type') === 'chatroom') { + if (chatbox && chatbox.get('type') === _converse.CHATROOMS_TYPE) { return false; } if (message.getAttribute('type') !== 'error' &&