diff --git a/dist/converse.js b/dist/converse.js index c1088b67c..1d29c0193 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -62023,23 +62023,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha return attrs; }, - async createMessage(stanza, original_stanza) { - const msgid = stanza.getAttribute('id'), - message = msgid && this.messages.findWhere({ - msgid - }); - - if (!message) { - // Only create the message when we're sure it's not a duplicate - const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza); - - if (attrs['chat_state'] || !u.isEmptyMessage(attrs)) { - const msg = this.messages.create(attrs); - this.incrementUnreadMsgCounter(msg); - } - } - }, - isHidden() { /* Returns a boolean to indicate whether a newly received * message will be visible to the user or not. @@ -62249,6 +62232,13 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha contact_jid = Strophe.getBareJidFromJid(to_jid); } else { contact_jid = from_bare_jid; + await _converse.api.waitUntil('rosterContactsFetched'); + + const roster_item = _converse.roster.get(contact_jid); + + if (_.isUndefined(roster_item) && !_converse.allow_non_roster_messaging) { + return; + } } // Get chat box, but only create when the message has something to show to the user @@ -62259,7 +62249,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha chatbox = this.getChatBox(contact_jid, chatbox_attrs, has_body); if (chatbox && !chatbox.handleMessageCorrection(stanza) && !chatbox.handleReceipt(stanza, from_jid, is_carbon, is_me) && !chatbox.handleChatMarker(stanza, from_jid, is_carbon)) { - await chatbox.createMessage(stanza, original_stanza); + const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza); + + if (attrs['chat_state'] || !u.isEmptyMessage(attrs)) { + const msg = chatbox.messages.create(attrs); + chatbox.incrementUnreadMsgCounter(msg); + } } _converse.emit('message', { @@ -66997,7 +66992,41 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc acknowledged[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length > 0; }, + reflectionHandled(stanza) { + /* Handle a MUC reflected message and return true if so. + * + * Parameters: + * (XMLElement) stanza: The message stanza + */ + const from = stanza.getAttribute('from'); + const own_message = Strophe.getResourceFromJid(from) == this.get('nick'); + + if (own_message) { + const msgid = stanza.getAttribute('id'), + jid = stanza.getAttribute('from'); // TODO: use stanza-id? + + if (msgid) { + const msg = this.messages.findWhere({ + 'msgid': msgid, + 'from': jid + }); + + if (msg && msg.get('sender') === 'me' && !msg.get('received')) { + msg.save({ + 'received': moment().format() + }); + return true; + } + } + } + }, + subjectChangeHandled(attrs) { + /* Handle a subject change and return `true` if so. + * + * Parameters: + * (Object) attrs: The message attributes + */ if (attrs.subject && !attrs.thread && !attrs.message) { // https://xmpp.org/extensions/xep-0045.html#subject-mod // ----------------------------------------------------- @@ -67016,6 +67045,18 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc return false; }, + ignorableCSN(attrs) { + /* Is this a chat state notification that can be ignored, + * because it's old or because it's from us. + * + * Parameters: + * (Object) attrs: The message attributes + */ + const is_csn = _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isOnlyChatStateNotification(attrs), + own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick'); + return is_csn && (attrs.is_delayed || own_message); + }, + async onMessage(stanza) { /* Handler for all MUC messages sent to this groupchat. * @@ -67040,16 +67081,9 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc return; } - if (!this.handleMessageCorrection(stanza) && !this.isReceipt(stanza) && !this.isChatMarker(stanza) && !this.subjectChangeHandled(attrs)) { - const is_csn = _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isOnlyChatStateNotification(attrs), - own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick'); - - if (is_csn && (attrs.is_delayed || own_message)) { - // No need showing delayed or our own CSN messages - return; - } - - const msg = await this.createMessage(stanza, original_stanza); + if (!this.handleMessageCorrection(stanza) && !this.isReceipt(stanza) && !this.isChatMarker(stanza) && !this.reflectionHandled(stanza) && !this.subjectChangeHandled(attrs) && !this.ignorableCSN(attrs) && (attrs['chat_state'] || !_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isEmptyMessage(attrs))) { + const msg = this.messages.create(attrs); + this.incrementUnreadMsgCounter(msg); if (forwarded && msg && msg.get('sender') === 'me') { msg.save({ diff --git a/spec/chatbox.js b/spec/chatbox.js index 48c16072a..0ed33f9e7 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -144,6 +144,7 @@ null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true}, async function (done, _converse) { + _converse.emit('rosterContactsFetched'); const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const stanza = u.toStanza(` _converse.api.listen.on('message', resolve)); _converse.connection._dataRecv(test_utils.createRequest(stanza)); + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); await test_utils.waitUntil(() => message_promise); expect(_converse.chatboxviews.keys().length).toBe(2); done(); @@ -568,6 +570,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); spyOn(_converse, 'emit'); @@ -581,6 +584,7 @@ }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); await _converse.chatboxes.onMessage(msg); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); + expect(_converse.api.chats.get().length).toBe(1); done(); })); @@ -1054,6 +1058,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current', 3); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); spyOn(_converse, 'emit'); @@ -1209,6 +1214,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); // initial state expect(_converse.msg_counter).toBe(0); const message = 'This message will always increment the message counter from zero', @@ -1228,9 +1234,8 @@ // leave converse-chat page _converse.windowState = 'hidden'; _converse.chatboxes.onMessage(msgFactory()); - await test_utils.waitUntil(() => _converse.api.chats.get().length) + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2) let view = _converse.chatboxviews.get(sender_jid); - await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(_converse.msg_counter).toBe(1); // come back to converse-chat page @@ -1244,9 +1249,8 @@ // check that msg_counter is incremented from zero again _converse.chatboxes.onMessage(msgFactory()); - await test_utils.waitUntil(() => _converse.api.chats.get().length) + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2) view = _converse.chatboxviews.get(sender_jid); - await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(u.isVisible(view.el)).toBeTruthy(); expect(_converse.msg_counter).toBe(1); done(); diff --git a/spec/messages.js b/spec/messages.js index 2637b3375..d7ef7ac32 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -538,6 +538,7 @@ expect(msg_obj.get('is_delayed')).toEqual(false); // Now check that the message appears inside the chatbox in the DOM const chat_content = view.el.querySelector('.chat-content'); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); 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(); await test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Candice van der Knijff') @@ -552,6 +553,7 @@ await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']); test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); // Send a message from a different resource @@ -579,6 +581,7 @@ const view = _converse.chatboxviews.get(recipient_jid); expect(chatbox).toBeDefined(); expect(view).toBeDefined(); + await new Promise(resolve => view.once('messageInserted', resolve)); // Check that the message was received and check the message parameters expect(chatbox.messages.length).toEqual(1); const msg_obj = chatbox.messages.models[0]; @@ -1030,6 +1033,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); let message, msg; @@ -1050,6 +1054,7 @@ 'id': (new Date()).getTime() }).c('body').t('A message').up() .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + await new Promise(resolve => _converse.on('chatBoxOpened', resolve)); const view = await _converse.chatboxviews.get(sender_jid); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); @@ -1195,6 +1200,7 @@ null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const msg_id = u.getUniqueId(); const sent_stanzas = []; @@ -1209,7 +1215,7 @@ }).c('body').t('Message!').up() .c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree(); await _converse.chatboxes.onMessage(msg); - const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, sent_stanzas[0].tree()).pop(); + const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, sent_stanzas[1].tree()).pop(); expect(Strophe.serialize(receipt)).toBe(``); done(); })); @@ -1341,6 +1347,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300); spyOn(_converse, 'emit'); @@ -1357,12 +1364,11 @@ }).c('body').t(message).up() .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree() ); - await test_utils.waitUntil(() => _converse.api.chats.get().length); + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); const chatbox = _converse.chatboxes.get(sender_jid); expect(chatbox).toBeDefined(); const view = _converse.chatboxviews.get(sender_jid); expect(view).toBeDefined(); - await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); // Check that the message was received and check the message parameters @@ -1449,6 +1455,7 @@ null, ['rosterGroupsFetched'], {}, async function (done, _converse) { + _converse.emit('rosterContactsFetched'); _converse.allow_non_roster_messaging = true; spyOn(_converse, 'emit').and.callThrough(); @@ -1485,6 +1492,7 @@ expect(chatbox).toBeDefined(); expect(view).toBeDefined(); expect(chatbox.get('fullname') === sender_jid); + await new Promise(resolve => view.once('messageInserted', resolve)); await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Max Frankfurter'); let author_el = view.el.querySelector('.chat-msg__author'); @@ -1507,6 +1515,7 @@ async function (done, _converse) { _converse.allow_non_roster_messaging = false; + _converse.emit('rosterContactsFetched'); spyOn(_converse, 'emit'); const message = 'This is a received message from someone not on the roster'; @@ -1526,15 +1535,15 @@ expect(chatbox).not.toBeDefined(); // onMessage is a handler for received XMPP messages await _converse.chatboxes.onMessage(msg); + expect(_converse.api.chats.get().length).toBe(1); + let view = _converse.chatboxviews.get(sender_jid); + expect(view).not.toBeDefined(); - await test_utils.waitUntil(() => _converse.api.chats.get().length) - const view = _converse.chatboxviews.get(sender_jid); - - expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); // onMessage is a handler for received XMPP messages - _converse.allow_non_roster_messaging =true; + _converse.allow_non_roster_messaging = true; await _converse.chatboxes.onMessage(msg); - await test_utils.waitUntil(() => view.model.messages.length); + view = _converse.chatboxviews.get(sender_jid); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); // Check that the chatbox and its view now exist chatbox = _converse.chatboxes.get(sender_jid); @@ -2109,7 +2118,6 @@ null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { - test_utils.createContacts(_converse, 'current'); await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); } @@ -2128,6 +2136,37 @@ })); + it("can not be expected to have a unique id attribute", + mock.initConverse( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + async function (done, _converse) { + + await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + const view = _converse.chatboxviews.get('lounge@localhost'); + if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); } + const id = u.getUniqueId(); + let msg = $msg({ + from: 'lounge@localhost/some1', + id: id, + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t('First message').tree(); + await view.model.onMessage(msg); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + + msg = $msg({ + from: 'lounge@localhost/some2', + id: id, + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t('Another message').tree(); + await view.model.onMessage(msg); + await new Promise((resolve, reject) => view.once('messageInserted', resolve)); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); + done(); + })); + it("keeps track whether you are the sender or not", mock.initConverse( null, ['rosterGroupsFetched'], {}, diff --git a/spec/notification.js b/spec/notification.js index 48efdf6ac..2212d2335 100644 --- a/spec/notification.js +++ b/spec/notification.js @@ -72,7 +72,8 @@ to: 'dummy@localhost', type: 'groupchat' }).c('body').t(message).tree(); - await _converse.chatboxes.onMessage(msg); // This will emit 'message' + + _converse.connection._dataRecv(test_utils.createRequest(msg)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled(); expect(_converse.showMessageNotification).toHaveBeenCalled(); diff --git a/spec/spoilers.js b/spec/spoilers.js index 3fda9dcaa..199a920f4 100644 --- a/spec/spoilers.js +++ b/spec/spoilers.js @@ -15,6 +15,7 @@ async (done, _converse) => { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; /* @@ -36,6 +37,7 @@ .tree(); _converse.chatboxes.onMessage(msg); + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); const view = _converse.chatboxviews.get(sender_jid); await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter') expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter'); @@ -52,6 +54,7 @@ async (done, _converse) => { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; /* * And at the end of the story, both of them die! It is so tragic! @@ -69,6 +72,7 @@ 'xmlns': 'urn:xmpp:spoiler:0', }).tree(); _converse.chatboxes.onMessage(msg); + await test_utils.waitUntil(() => _converse.api.chats.get().length === 2); const view = _converse.chatboxviews.get(sender_jid); await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter') expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy();