From 33600eeeced8975ebe3c6a28fd3419b91e0b1735 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 7 Mar 2019 15:44:58 +0100 Subject: [PATCH] No need for a separate `archive_id` value. With MAM2 we can just use stanza-id --- dist/converse.js | 214 ++++++++++++++-------------- spec/mam.js | 219 ++++++++++++++++++----------- spec/messages.js | 24 ++-- src/headless/converse-chatboxes.js | 65 ++++----- src/headless/converse-mam.js | 82 ++++++----- src/headless/converse-muc.js | 57 ++++---- 6 files changed, 356 insertions(+), 305 deletions(-) diff --git a/dist/converse.js b/dist/converse.js index c30453fc2..9f4606eec 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -61911,6 +61911,9 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha return this.vcard.get('fullname') || this.get('jid'); }, + updateMessage(message, stanza) {// Overridden in converse-muc and converse-mam + }, + handleMessageCorrection(stanza) { const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop(); @@ -61942,11 +61945,15 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha return false; }, + getDuplicateMessage(stanza) { + return this.findDuplicateFromOriginID(stanza) || this.findDuplicateFromStanzaID(stanza); + }, + findDuplicateFromOriginID(stanza) { const origin_id = sizzle(`origin-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); if (!origin_id) { - return false; + return null; } return this.messages.findWhere({ @@ -61955,27 +61962,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha }); }, - async hasDuplicateArchiveID(stanza) { - const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); - - if (!result) { - return false; - } - - const by_jid = stanza.getAttribute('from') || this.get('jid'); - const supported = await _converse.api.disco.supports(Strophe.NS.MAM, by_jid); - - if (!supported.length) { - return false; - } - - const query = {}; - query[`stanza_id ${by_jid}`] = result.getAttribute('id'); - const msg = this.messages.findWhere(query); - return !_.isNil(msg); - }, - - async hasDuplicateStanzaID(stanza) { + async findDuplicateFromStanzaID(stanza) { const stanza_id = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); if (!stanza_id) { @@ -61991,8 +61978,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha const query = {}; query[`stanza_id ${by_jid}`] = stanza_id.getAttribute('id'); - const msg = this.messages.findWhere(query); - return !_.isNil(msg); + return this.messages.findWhere(query); }, sendMarker(to_jid, id, type) { @@ -62349,6 +62335,10 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha return attrs; }, + isArchived(original_stanza) { + return !_.isNil(sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop()); + }, + getMessageAttributesFromStanza(stanza, original_stanza) { /* Parses a passed in message stanza and returns an object * of attributes. @@ -62361,15 +62351,14 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha * that contains the message stanza, if it was * contained, otherwise it's the message stanza itself. */ - const archive = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop(), - spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), + const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(), text = _converse.chatboxes.getMessageBody(stanza) || undefined, chat_state = stanza.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING || stanza.getElementsByTagName(_converse.PAUSED).length && _converse.PAUSED || stanza.getElementsByTagName(_converse.INACTIVE).length && _converse.INACTIVE || stanza.getElementsByTagName(_converse.ACTIVE).length && _converse.ACTIVE || stanza.getElementsByTagName(_converse.GONE).length && _converse.GONE; const attrs = _.extend({ 'chat_state': chat_state, - 'is_archived': !_.isNil(archive), + 'is_archived': this.isArchived(original_stanza), 'is_delayed': !_.isNil(delay), 'is_spoiler': !_.isNil(spoiler), 'is_single_emoji': text ? u.isSingleEmoji(text) : false, @@ -62637,12 +62626,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha 'nickname': roster_nick }, has_body); - if (chatbox && !chatbox.findDuplicateFromOriginID(stanza) && !(await chatbox.hasDuplicateArchiveID(original_stanza)) && !(await chatbox.hasDuplicateStanzaID(stanza)) && !chatbox.handleMessageCorrection(stanza) && !chatbox.handleReceipt(stanza, from_jid, is_carbon, is_me) && !chatbox.handleChatMarker(stanza, from_jid, is_carbon, is_roster_contact)) { - const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza); + if (chatbox) { + const message = await chatbox.getDuplicateMessage(stanza); - if (attrs['chat_state'] || !u.isEmptyMessage(attrs)) { - const msg = chatbox.messages.create(attrs); - chatbox.incrementUnreadMsgCounter(msg); + if (message) { + chatbox.updateMessage(message, original_stanza); + } + + if (!message && !chatbox.handleMessageCorrection(stanza) && !chatbox.handleReceipt(stanza, from_jid, is_carbon, is_me) && !chatbox.handleChatMarker(stanza, from_jid, is_carbon, is_roster_contact)) { + const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza); + + if (attrs['chat_state'] || !u.isEmptyMessage(attrs)) { + const msg = chatbox.messages.create(attrs); + chatbox.incrementUnreadMsgCounter(msg); + } } } @@ -65603,18 +65600,6 @@ const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'cou const MAM_ATTRIBUTES = ['with', 'start', 'end']; -function getMessageArchiveID(stanza) { - // See https://xmpp.org/extensions/xep-0313.html#results - // - // The result messages MUST contain a element with an 'id' - // attribute that gives the current message's archive UID - const result = sizzle__WEBPACK_IMPORTED_MODULE_3___default()(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); - - if (!_.isUndefined(result)) { - return result.getAttribute('id'); - } -} - function queryForArchivedMessages(_converse, options, callback, errback) { /* Internal function, called by the "archive.query" API method. */ @@ -65739,10 +65724,44 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam // // New functions which don't exist yet can also be added. ChatBox: { - async getMessageAttributesFromStanza(message, original_stanza) { - const attrs = await this.__super__.getMessageAttributesFromStanza.apply(this, arguments); - attrs.archive_id = getMessageArchiveID(original_stanza); - return attrs; + async findDuplicateFromArchiveID(stanza) { + const _converse = this.__super__._converse; + const result = sizzle__WEBPACK_IMPORTED_MODULE_3___default()(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); + + if (!result) { + return null; + } + + const by_jid = stanza.getAttribute('from') || this.get('jid'); + const supported = await _converse.api.disco.supports(Strophe.NS.MAM, by_jid); + + if (!supported.length) { + return null; + } + + const query = {}; + query[`stanza_id ${by_jid}`] = result.getAttribute('id'); + return this.messages.findWhere(query); + }, + + async getDuplicateMessage(stanza) { + const message = await this.__super__.getDuplicateMessage.apply(this, arguments); + + if (!message) { + return this.findDuplicateFromArchiveID(stanza); + } + + return message; + }, + + updateMessage(message, stanza) { + this.__super__.updateMessage.apply(this, arguments); + + if (message && !message.get('is_archived')) { + message.save(_.extend({ + 'is_archived': this.isArchived(stanza) + }, this.getStanzaIDs(stanza))); + } } }, @@ -65771,11 +65790,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam if (_.isNil(most_recent_msg)) { this.fetchArchivedMessages(); } else { - const archive_id = most_recent_msg.get('archive_id'); + const stanza_id = most_recent_msg.get(`stanza_id ${this.model.get('jid')}`); - if (archive_id) { + if (stanza_id) { this.fetchArchivedMessages({ - 'after': most_recent_msg.get('archive_id') + 'after': stanza_id }); } else { this.fetchArchivedMessages({ @@ -65874,11 +65893,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam if (this.content.scrollTop === 0 && this.model.messages.length) { const oldest_message = this.model.messages.at(0); - const archive_id = oldest_message.get('archive_id'); + const by_jid = this.model.get('jid'); + const stanza_id = oldest_message.get(`stanza_id ${by_jid}`); - if (archive_id) { + if (stanza_id) { this.fetchArchivedMessages({ - 'before': archive_id + 'before': stanza_id }); } else { this.fetchArchivedMessages({ @@ -65888,24 +65908,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam } } - }, - ChatRoom: { - isDuplicate(message, original_stanza) { - const result = this.__super__.isDuplicate.apply(this, arguments); - - if (result) { - return result; - } - - const archive_id = getMessageArchiveID(original_stanza); - - if (archive_id) { - return this.messages.filter({ - 'archive_id': archive_id - }).length > 0; - } - } - }, ChatRoomView: { initialize() { @@ -67350,39 +67352,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc acknowledged[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length > 0; }, - handleReflection(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 msg = this.findDuplicateFromOriginID(stanza); - - if (msg) { - const attrs = {}; - 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'); - } - - if (!msg.get('received')) { - attrs.received = moment().format(); - } - - msg.save(attrs); - } - - return msg ? true : false; - } - }, - subjectChangeHandled(attrs) { /* Handle a subject change and return `true` if so. * @@ -67419,6 +67388,33 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc return is_csn && (attrs.is_delayed || own_message); }, + updateMessage(message, stanza) { + /* Make sure that the already cached message is updated with + * the stanza ID. + */ + _converse.ChatBox.prototype.updateMessage.call(this, message, stanza); + + const from = stanza.getAttribute('from'); + const own_message = Strophe.getResourceFromJid(from) == this.get('nick'); + + if (own_message) { + const attrs = {}; + 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'); + } + + if (!message.get('received')) { + attrs.received = moment().format(); + } + + message.save(attrs); + } + }, + async onMessage(stanza) { /* Handler for all MUC messages sent to this groupchat. * @@ -67433,7 +67429,13 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc stanza = forwarded.querySelector('message'); } - if (this.handleReflection(stanza) || (await this.hasDuplicateArchiveID(original_stanza)) || (await this.hasDuplicateStanzaID(stanza)) || this.handleMessageCorrection(stanza) || this.isReceipt(stanza) || this.isChatMarker(stanza)) { + const message = await this.getDuplicateMessage(original_stanza); + + if (message) { + this.updateMessage(message, original_stanza); + } + + if (message || this.handleMessageCorrection(stanza) || this.isReceipt(stanza) || this.isChatMarker(stanza)) { return _converse.emit('message', { 'stanza': original_stanza }); diff --git a/spec/mam.js b/spec/mam.js index 3569b5abc..1b830a614 100644 --- a/spec/mam.js +++ b/spec/mam.js @@ -14,96 +14,147 @@ describe("Message Archive Management", function () { // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config - describe("Archived Messages", function () { + describe("An archived message", function () { - it("aren't shown as duplicates by comparing their stanza id and archive id", - mock.initConverse( - null, ['discoInitialized'], {}, - async function (done, _converse) { + describe("when recieved", function () { - await test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand'); - const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org'); - let stanza = u.toStanza( - ` - negan - - `); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); - // Not sure whether such a race-condition might pose a problem - // in "real-world" situations. - stanza = u.toStanza( - ` - - - - - negan - - - - `); - spyOn(view.model, 'hasDuplicateArchiveID').and.callThrough(); - view.model.onMessage(stanza); - await test_utils.waitUntil(() => view.model.hasDuplicateArchiveID.calls.count()); - expect(view.model.hasDuplicateArchiveID.calls.count()).toBe(1); - const result = await view.model.hasDuplicateArchiveID.calls.all()[0].returnValue - expect(result).toBe(true); - expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); - done(); - })); + it("updates the is_archived value of an already cached version", + mock.initConverse( + null, ['discoInitialized'], {}, + async function (done, _converse) { - it("aren't shown as duplicates by comparing only their archive id", - mock.initConverse( - null, ['discoInitialized'], {}, - async function (done, _converse) { + await test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'dummy'); - await test_utils.openAndEnterChatRoom(_converse, 'discuss', 'conference.conversejs.org', 'dummy'); - const view = _converse.chatboxviews.get('discuss@conference.conversejs.org'); - let stanza = u.toStanza( - ` - - - - - looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published - - - - - - - `); - view.model.onMessage(stanza); - await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); - expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); + const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org'); + let stanza = u.toStanza( + ` + Hello + + `); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); + expect(view.model.messages.length).toBe(1); + expect(view.model.messages.at(0).get('is_archived')).toBe(false); + expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621'); - stanza = u.toStanza( - ` - - - - - looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published - - - - - - - `); + stanza = u.toStanza( + ` + + + + + Hello + + + + `); + spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough(); + spyOn(view.model, 'updateMessage').and.callThrough(); + view.model.onMessage(stanza); + await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count()); + expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1); + const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue + expect(result instanceof _converse.Message).toBe(true); + expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); - spyOn(view.model, 'hasDuplicateArchiveID').and.callThrough(); - view.model.onMessage(stanza); - await test_utils.waitUntil(() => view.model.hasDuplicateArchiveID.calls.count()); - expect(view.model.hasDuplicateArchiveID.calls.count()).toBe(1); - const result = await view.model.hasDuplicateArchiveID.calls.all()[0].returnValue - expect(result).toBe(true); - expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); - done(); - })) + await test_utils.waitUntil(() => view.model.updateMessage.calls.count()); + expect(view.model.messages.length).toBe(1); + expect(view.model.messages.at(0).get('is_archived')).toBe(true); + expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621'); + done(); + })); + + it("isn't shown as duplicate by comparing its stanza id or archive id", + mock.initConverse( + null, ['discoInitialized'], {}, + async function (done, _converse) { + + await test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand'); + const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org'); + let stanza = u.toStanza( + ` + negan + + `); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); + // Not sure whether such a race-condition might pose a problem + // in "real-world" situations. + stanza = u.toStanza( + ` + + + + + negan + + + + `); + spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough(); + view.model.onMessage(stanza); + await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count()); + expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1); + const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue + expect(result instanceof _converse.Message).toBe(true); + expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); + done(); + })); + + it("isn't shown as duplicate by comparing only the archive id", + mock.initConverse( + null, ['discoInitialized'], {}, + async function (done, _converse) { + + await test_utils.openAndEnterChatRoom(_converse, 'discuss', 'conference.conversejs.org', 'dummy'); + const view = _converse.chatboxviews.get('discuss@conference.conversejs.org'); + let stanza = u.toStanza( + ` + + + + + looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published + + + + + + + `); + view.model.onMessage(stanza); + await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); + expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); + + stanza = u.toStanza( + ` + + + + + looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published + + + + + + + `); + + spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough(); + view.model.onMessage(stanza); + await test_utils.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count()); + expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1); + const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue + expect(result instanceof _converse.Message).toBe(true); + expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); + done(); + })) + }); }); describe("The archive.query API", function () { diff --git a/spec/messages.js b/spec/messages.js index 21a0cda30..b902c9761 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -2223,7 +2223,7 @@ await test_utils.openAndEnterChatRoom(_converse, 'room', 'muc.example.com', 'dummy'); const view = _converse.chatboxviews.get('room@muc.example.com'); - spyOn(view.model, 'hasDuplicateStanzaID').and.callThrough(); + spyOn(view.model, 'findDuplicateFromStanzaID').and.callThrough(); let stanza = u.toStanza(` _converse.api.chats.get().length); await test_utils.waitUntil(() => view.model.messages.length === 1); - await test_utils.waitUntil(() => view.model.hasDuplicateStanzaID.calls.count() === 1); - let result = await view.model.hasDuplicateStanzaID.calls.all()[0].returnValue; - expect(result).toBe(false); + await test_utils.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 1); + let result = await view.model.findDuplicateFromStanzaID.calls.all()[0].returnValue; + expect(result).toBe(undefined); stanza = u.toStanza(` `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); - await test_utils.waitUntil(() => view.model.hasDuplicateStanzaID.calls.count() === 2); - result = await view.model.hasDuplicateStanzaID.calls.all()[1].returnValue; - expect(result).toBe(true); + await test_utils.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 2); + result = await view.model.findDuplicateFromStanzaID.calls.all()[1].returnValue; + expect(result instanceof _converse.Message).toBe(true); expect(view.model.messages.length).toBe(1); done(); })); @@ -2477,7 +2477,13 @@ `); await view.model.onMessage(stanza); + await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg__receipt').length); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1); + expect(view.model.messages.length).toBe(1); + + const message = view.model.messages.at(0); + expect(message.get('stanza_id lounge@localhost')).toBe('5f3dbc5e-e1d3-4077-a492-693f3769c7ad'); + expect(message.get('origin_id')).toBe(msg_obj.get('origin_id')); done(); })); @@ -2518,9 +2524,9 @@ by="room@muc.example.com"/> `); - spyOn(view.model, 'handleReflection').and.callThrough(); + spyOn(view.model, 'updateMessage').and.callThrough(); _converse.connection._dataRecv(test_utils.createRequest(stanza)); - await test_utils.waitUntil(() => view.model.handleReflection.calls.count() === 1); + await test_utils.waitUntil(() => view.model.updateMessage.calls.count() === 1); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad"); expect(view.model.messages.at(0).get('origin_id')).toBe(attrs.origin_id); diff --git a/src/headless/converse-chatboxes.js b/src/headless/converse-chatboxes.js index a281034a1..dde792446 100644 --- a/src/headless/converse-chatboxes.js +++ b/src/headless/converse-chatboxes.js @@ -290,6 +290,10 @@ converse.plugins.add('converse-chatboxes', { return this.vcard.get('fullname') || this.get('jid'); }, + updateMessage (message, stanza) { + // Overridden in converse-muc and converse-mam + }, + handleMessageCorrection (stanza) { const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop(); if (replace) { @@ -316,10 +320,14 @@ converse.plugins.add('converse-chatboxes', { return false; }, + getDuplicateMessage (stanza) { + return this.findDuplicateFromOriginID(stanza) || this.findDuplicateFromStanzaID(stanza); + }, + findDuplicateFromOriginID (stanza) { const origin_id = sizzle(`origin-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); if (!origin_id) { - return false; + return null; } return this.messages.findWhere({ 'origin_id': origin_id.getAttribute('id'), @@ -327,23 +335,7 @@ converse.plugins.add('converse-chatboxes', { }); }, - async hasDuplicateArchiveID (stanza) { - const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); - if (!result) { - return false; - } - const by_jid = stanza.getAttribute('from') || this.get('jid'); - const supported = await _converse.api.disco.supports(Strophe.NS.MAM, by_jid); - if (!supported.length) { - return false; - } - const query = {}; - query[`stanza_id ${by_jid}`] = result.getAttribute('id'); - const msg = this.messages.findWhere(query); - return !_.isNil(msg); - }, - - async hasDuplicateStanzaID (stanza) { + async findDuplicateFromStanzaID(stanza) { const stanza_id = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); if (!stanza_id) { return false; @@ -355,8 +347,7 @@ converse.plugins.add('converse-chatboxes', { } const query = {}; query[`stanza_id ${by_jid}`] = stanza_id.getAttribute('id'); - const msg = this.messages.findWhere(query); - return !_.isNil(msg); + return this.messages.findWhere(query); }, @@ -654,6 +645,10 @@ converse.plugins.add('converse-chatboxes', { return attrs; }, + isArchived (original_stanza) { + return !_.isNil(sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop()); + }, + getMessageAttributesFromStanza (stanza, original_stanza) { /* Parses a passed in message stanza and returns an object * of attributes. @@ -666,8 +661,7 @@ converse.plugins.add('converse-chatboxes', { * that contains the message stanza, if it was * contained, otherwise it's the message stanza itself. */ - const archive = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop(), - spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), + const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(), text = _converse.chatboxes.getMessageBody(stanza) || undefined, chat_state = stanza.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING || @@ -678,7 +672,7 @@ converse.plugins.add('converse-chatboxes', { const attrs = _.extend({ 'chat_state': chat_state, - 'is_archived': !_.isNil(archive), + 'is_archived': this.isArchived(original_stanza), 'is_delayed': !_.isNil(delay), 'is_spoiler': !_.isNil(spoiler), 'is_single_emoji': text ? u.isSingleEmoji(text) : false, @@ -926,18 +920,21 @@ converse.plugins.add('converse-chatboxes', { roster_nick = _.get(_converse.api.contacts.get(contact_jid), 'attributes.nickname'), chatbox = this.getChatBox(contact_jid, {'nickname': roster_nick}, has_body); - if (chatbox && - !chatbox.findDuplicateFromOriginID(stanza) && - !await chatbox.hasDuplicateArchiveID(original_stanza) && - !await chatbox.hasDuplicateStanzaID(stanza) && - !chatbox.handleMessageCorrection(stanza) && - !chatbox.handleReceipt (stanza, from_jid, is_carbon, is_me) && - !chatbox.handleChatMarker(stanza, from_jid, is_carbon, is_roster_contact)) { + if (chatbox) { + const message = await chatbox.getDuplicateMessage(stanza); + if (message) { + chatbox.updateMessage(message, original_stanza); + } + if (!message && + !chatbox.handleMessageCorrection(stanza) && + !chatbox.handleReceipt (stanza, from_jid, is_carbon, is_me) && + !chatbox.handleChatMarker(stanza, from_jid, is_carbon, is_roster_contact)) { - const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza); - if (attrs['chat_state'] || !u.isEmptyMessage(attrs)) { - const msg = chatbox.messages.create(attrs); - chatbox.incrementUnreadMsgCounter(msg); + 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', {'stanza': original_stanza, 'chatbox': chatbox}); diff --git a/src/headless/converse-mam.js b/src/headless/converse-mam.js index 1aa26910a..ed902b434 100644 --- a/src/headless/converse-mam.js +++ b/src/headless/converse-mam.js @@ -23,17 +23,6 @@ const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'cou const MAM_ATTRIBUTES = ['with', 'start', 'end']; -function getMessageArchiveID (stanza) { - // See https://xmpp.org/extensions/xep-0313.html#results - // - // The result messages MUST contain a element with an 'id' - // attribute that gives the current message's archive UID - const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); - if (!_.isUndefined(result)) { - return result.getAttribute('id'); - } -} - function queryForArchivedMessages (_converse, options, callback, errback) { /* Internal function, called by the "archive.query" API method. */ @@ -128,10 +117,38 @@ converse.plugins.add('converse-mam', { // New functions which don't exist yet can also be added. ChatBox: { - async getMessageAttributesFromStanza (message, original_stanza) { - const attrs = await this.__super__.getMessageAttributesFromStanza.apply(this, arguments); - attrs.archive_id = getMessageArchiveID(original_stanza); - return attrs; + async findDuplicateFromArchiveID (stanza) { + const { _converse } = this.__super__; + const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); + if (!result) { + return null; + } + const by_jid = stanza.getAttribute('from') || this.get('jid'); + const supported = await _converse.api.disco.supports(Strophe.NS.MAM, by_jid); + if (!supported.length) { + return null; + } + const query = {}; + query[`stanza_id ${by_jid}`] = result.getAttribute('id'); + return this.messages.findWhere(query); + }, + + async getDuplicateMessage (stanza) { + const message = await this.__super__.getDuplicateMessage.apply(this, arguments); + if (!message) { + return this.findDuplicateFromArchiveID(stanza); + } + return message; + }, + + + updateMessage (message, stanza) { + this.__super__.updateMessage.apply(this, arguments); + if (message && !message.get('is_archived')) { + message.save(_.extend({ + 'is_archived': this.isArchived(stanza) + }, this.getStanzaIDs(stanza))); + } } }, @@ -155,15 +172,11 @@ converse.plugins.add('converse-mam', { if (_.isNil(most_recent_msg)) { this.fetchArchivedMessages(); } else { - const archive_id = most_recent_msg.get('archive_id'); - if (archive_id) { - this.fetchArchivedMessages({ - 'after': most_recent_msg.get('archive_id') - }); + const stanza_id = most_recent_msg.get(`stanza_id ${this.model.get('jid')}`); + if (stanza_id) { + this.fetchArchivedMessages({'after': stanza_id}); } else { - this.fetchArchivedMessages({ - 'start': most_recent_msg.get('time') - }); + this.fetchArchivedMessages({'start': most_recent_msg.get('time')}); } } }, @@ -250,11 +263,10 @@ converse.plugins.add('converse-mam', { const { _converse } = this.__super__; if (this.content.scrollTop === 0 && this.model.messages.length) { const oldest_message = this.model.messages.at(0); - const archive_id = oldest_message.get('archive_id'); - if (archive_id) { - this.fetchArchivedMessages({ - 'before': archive_id - }); + const by_jid = this.model.get('jid'); + const stanza_id = oldest_message.get(`stanza_id ${by_jid}`); + if (stanza_id) { + this.fetchArchivedMessages({'before': stanza_id}); } else { this.fetchArchivedMessages({ 'end': oldest_message.get('time') @@ -264,20 +276,6 @@ converse.plugins.add('converse-mam', { }, }, - ChatRoom: { - - isDuplicate (message, original_stanza) { - const result = this.__super__.isDuplicate.apply(this, arguments); - if (result) { - return result; - } - const archive_id = getMessageArchiveID(original_stanza); - if (archive_id) { - return this.messages.filter({'archive_id': archive_id}).length > 0; - } - } - }, - ChatRoomView: { initialize () { diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index 015ef7496..a55b909dc 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -972,33 +972,6 @@ converse.plugins.add('converse-muc', { acknowledged[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length > 0; }, - handleReflection (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 msg = this.findDuplicateFromOriginID(stanza); - if (msg) { - const attrs = {}; - 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'); - } - if (!msg.get('received')) { - attrs.received = moment().format(); - } - msg.save(attrs); - } - return msg ? true : false; - } - }, - subjectChangeHandled (attrs) { /* Handle a subject change and return `true` if so. * @@ -1029,6 +1002,28 @@ converse.plugins.add('converse-muc', { return is_csn && (attrs.is_delayed || own_message); }, + updateMessage (message, stanza) { + /* Make sure that the already cached message is updated with + * the stanza ID. + */ + _converse.ChatBox.prototype.updateMessage.call(this, message, stanza); + const from = stanza.getAttribute('from'); + const own_message = Strophe.getResourceFromJid(from) == this.get('nick'); + if (own_message) { + const attrs = {}; + 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'); + } + if (!message.get('received')) { + attrs.received = moment().format(); + } + message.save(attrs); + } + }, + async onMessage (stanza) { /* Handler for all MUC messages sent to this groupchat. * @@ -1042,9 +1037,11 @@ converse.plugins.add('converse-muc', { if (forwarded) { stanza = forwarded.querySelector('message'); } - if (this.handleReflection(stanza) || - await this.hasDuplicateArchiveID(original_stanza) || - await this.hasDuplicateStanzaID(stanza) || + const message = await this.getDuplicateMessage(original_stanza); + if (message) { + this.updateMessage(message, original_stanza); + } + if (message || this.handleMessageCorrection(stanza) || this.isReceipt(stanza) || this.isChatMarker(stanza)) {