diff --git a/.eslintrc.json b/.eslintrc.json index 50215e4a1..44f0f1159 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ "split", "trim", "forEach", "toUpperCase", "includes", "values" ] }], + "lodash/import-scope": "off", "lodash/prefer-invoke-map": "off", "lodash/prefer-startswith": "off", "lodash/prefer-constant": "off", diff --git a/CHANGES.md b/CHANGES.md index e0218e303..f99a8002a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ - New API method [\_converse.api.disco.features.get](https://conversejs.org/docs/html/api/-_converse.api.disco.features.html#.get) - New config setting [muc_show_join_leave_status](https://conversejs.org/docs/html/configuration.html#muc-show-join-leave-status) - New event: `chatBoxBlurred`. +- New event: [chatBoxBlurred](https://conversejs.org/docs/html/api/-_converse.html#event:chatBoxBlurred) +- New event: [chatReconnected](https://conversejs.org/docs/html/api/-_converse.html#event:chatReconnected) - Properly handle message correction being received before the corrected message - #1296: `embedded` view mode shows `chatbox-navback` arrow in header - #1465: When highlighting a roster contact, they're incorrectly shown as online diff --git a/docs/source/_templates/jsdoc_layout.tmpl b/docs/source/_templates/jsdoc_layout.tmpl index 798e1d6de..aca52b0d6 100644 --- a/docs/source/_templates/jsdoc_layout.tmpl +++ b/docs/source/_templates/jsdoc_layout.tmpl @@ -10,15 +10,15 @@ - - + +

- * element of your website via the *script* and *link* tags: .. code-block:: html - + diff --git a/src/converse-controlbox.js b/src/converse-controlbox.js index 845f1100c..88c0d13c4 100644 --- a/src/converse-controlbox.js +++ b/src/converse-controlbox.js @@ -199,7 +199,9 @@ converse.plugins.add('converse-controlbox', { 'type': _converse.CONTROLBOX_TYPE, 'url': '' } - } + }, + + onReconnection: _.noop }); diff --git a/src/converse-mam-views.js b/src/converse-mam-views.js index 79a5ea63f..659da1d35 100644 --- a/src/converse-mam-views.js +++ b/src/converse-mam-views.js @@ -7,16 +7,12 @@ // Views for XEP-0313 Message Archive Management import converse from "@converse/headless/converse-core"; - - -const CHATROOMS_TYPE = 'chatroom'; -const { Strophe, _ } = converse.env; -const u = converse.env.utils; +import { debounce } from 'lodash' converse.plugins.add('converse-mam-views', { - dependencies: ['converse-disco', 'converse-mam', 'converse-chatview', 'converse-muc-views'], + dependencies: ['converse-mam', 'converse-chatview', 'converse-muc-views'], overrides: { // Overrides mentioned here will be picked up by converse.js's @@ -25,73 +21,11 @@ converse.plugins.add('converse-mam-views', { // // New functions which don't exist yet can also be added. - - ChatBox: { - fetchNewestMessages () { - /* Fetches messages that might have been archived *after* - * the last archived message in our local cache. - */ - if (this.disable_mam) { - return; - } - const { _converse } = this.__super__; - const most_recent_msg = u.getMostRecentMessage(this); - - if (_.isNil(most_recent_msg)) { - this.fetchArchivedMessages(); - } else { - const stanza_id = most_recent_msg.get(`stanza_id ${this.get('jid')}`); - if (stanza_id) { - this.fetchArchivedMessages({'after': stanza_id}); - } else { - this.fetchArchivedMessages({'start': most_recent_msg.get('time')}); - } - } - }, - - async fetchArchivedMessages (options) { - if (this.disable_mam) { - return; - } - const { _converse } = this.__super__; - const is_groupchat = this.get('type') === CHATROOMS_TYPE; - const mam_jid = is_groupchat ? this.get('jid') : _converse.bare_jid; - if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) { - return; - } - let message_handler; - if (is_groupchat) { - message_handler = this.onMessage.bind(this); - } else { - message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes) - } - let result = {}; - try { - result = await _converse.api.archive.query( - Object.assign({ - 'groupchat': is_groupchat, - 'before': '', // Page backwards from the most recent message - 'max': _converse.archived_messages_page_size, - 'with': this.get('jid'), - }, options)); - } catch (e) { - _converse.log( - "Error or timeout while trying to fetch "+ - "archived messages", Strophe.LogLevel.ERROR); - _converse.log(e, Strophe.LogLevel.ERROR); - } - if (result.messages) { - result.messages.forEach(message_handler); - } - } - }, - ChatBoxView: { - render () { const result = this.__super__.render.apply(this, arguments); if (!this.disable_mam) { - this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); + this.content.addEventListener('scroll', debounce(this.onScroll.bind(this), 100)); } return result; }, @@ -115,52 +49,14 @@ converse.plugins.add('converse-mam-views', { } }, - ChatRoom: { - - initialize () { - this.__super__.initialize.apply(this, arguments); - this.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this); - this.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this); - }, - - fetchArchivedMessagesIfNecessary () { - if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED || - !this.get('mam_enabled') || - this.get('mam_initialized')) { - return; - } - this.fetchArchivedMessages(); - this.save({'mam_initialized': true}); - } - }, - ChatRoomView: { - renderChatArea () { const result = this.__super__.renderChatArea.apply(this, arguments); if (!this.disable_mam) { - this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); + this.content.addEventListener('scroll', debounce(this.onScroll.bind(this), 100)); } return result; } } - }, - - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by Converse.js's plugin machinery. - */ - const { _converse } = this; - - /************************ BEGIN Event Handlers ************************/ - _converse.api.listen.on('afterMessagesFetched', chatbox => chatbox.fetchNewestMessages()); - - _converse.api.listen.on('reconnected', () => { - const private_chats = _converse.chatboxviews.filter( - view => _.at(view, 'model.attributes.type')[0] === 'chatbox' - ); - _.each(private_chats, view => view.model.fetchNewestMessages()) - }); - /************************ END Event Handlers ************************/ } }); diff --git a/src/headless/converse-chatboxes.js b/src/headless/converse-chatboxes.js index 31a26fc16..127bcbf06 100644 --- a/src/headless/converse-chatboxes.js +++ b/src/headless/converse-chatboxes.js @@ -352,6 +352,17 @@ converse.plugins.add('converse-chatboxes', { } }, + onReconnection () { + this.clearMessages(); + /** + * Triggered whenever a `_converse.ChatBox` instance has reconnected after an outage + * @event _converse#onChatReconnected + * @type {_converse.ChatBox | _converse.ChatRoom} + * @example _converse.api.listen.on('onChatReconnected', chatbox => { ... }); + */ + _converse.api.trigger('chatReconnected', this); + }, + validate (attrs, options) { const { _converse } = this.__super__; if (!attrs.jid) { @@ -1150,6 +1161,8 @@ converse.plugins.add('converse-chatboxes', { _converse.api.trigger('chatBoxesInitialized'); }); _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected()); + + _converse.api.listen.on('reconnected', () => _converse.chatboxes.forEach(m => m.onReconnection())); /************************ END Event Handlers ************************/ diff --git a/src/headless/converse-mam.js b/src/headless/converse-mam.js index f1f5dc4ad..21e0ce13f 100644 --- a/src/headless/converse-mam.js +++ b/src/headless/converse-mam.js @@ -23,7 +23,7 @@ const MAM_ATTRIBUTES = ['with', 'start', 'end']; converse.plugins.add('converse-mam', { - dependencies: ['converse-muc'], + dependencies: ['converse-disco', 'converse-muc'], overrides: { // Overrides mentioned here will be picked up by converse.js's @@ -33,6 +33,64 @@ converse.plugins.add('converse-mam', { // New functions which don't exist yet can also be added. ChatBox: { + fetchNewestMessages () { + /* Fetches messages that might have been archived *after* + * the last archived message in our local cache. + */ + if (this.disable_mam) { + return; + } + const { _converse } = this.__super__; + const most_recent_msg = u.getMostRecentMessage(this); + + if (_.isNil(most_recent_msg)) { + this.fetchArchivedMessages(); + } else { + const stanza_id = most_recent_msg.get(`stanza_id ${this.get('jid')}`); + if (stanza_id) { + this.fetchArchivedMessages({'after': stanza_id}); + } else { + this.fetchArchivedMessages({'start': most_recent_msg.get('time')}); + } + } + }, + + async fetchArchivedMessages (options) { + if (this.disable_mam) { + return; + } + const { _converse } = this.__super__; + const is_groupchat = this.get('type') === CHATROOMS_TYPE; + const mam_jid = is_groupchat ? this.get('jid') : _converse.bare_jid; + if (!(await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid))) { + return; + } + let message_handler; + if (is_groupchat) { + message_handler = this.onMessage.bind(this); + } else { + message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes) + } + let result = {}; + try { + result = await _converse.api.archive.query( + Object.assign({ + 'groupchat': is_groupchat, + 'before': '', // Page backwards from the most recent message + 'max': _converse.archived_messages_page_size, + 'with': this.get('jid'), + }, options)); + } catch (e) { + _converse.log( + "Error or timeout while trying to fetch "+ + "archived messages", Strophe.LogLevel.ERROR); + _converse.log(e, Strophe.LogLevel.ERROR); + } + if (result.messages) { + result.messages.forEach(message_handler); + } + }, + async findDuplicateFromArchiveID (stanza) { const { _converse } = this.__super__; const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); @@ -66,7 +124,26 @@ converse.plugins.add('converse-mam', { } return attrs; } - } + }, + + ChatRoom: { + initialize () { + this.__super__.initialize.apply(this, arguments); + this.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this); + this.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this); + }, + + fetchArchivedMessagesIfNecessary () { + if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED || + !this.get('mam_enabled') || + this.get('mam_initialized')) { + return; + } + this.fetchArchivedMessages(); + this.save({'mam_initialized': true}); + } + }, + }, initialize () { @@ -139,6 +216,8 @@ converse.plugins.add('converse-mam', { } }); + _converse.api.listen.on('afterMessagesFetched', chat => chat.fetchNewestMessages()); + _converse.api.listen.on('chatReconnected', chat => chat.fetchNewestMessages()); _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.MAM)); /************************ END Event Handlers ************************/ diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index f9aad5d8a..a6bb0f6da 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -240,7 +240,6 @@ converse.plugins.add('converse-muc', { } }, - async onConnectionStatusChanged () { if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) { this.occupants.fetchMembers(); @@ -254,6 +253,14 @@ converse.plugins.add('converse-muc', { } }, + onReconnection () { + this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); + this.clearMessages(); + this.registerHandlers(); + this.fetchMessages(); + this.join(); + }, + initFeatures () { const storage = _converse.config.get('storage'); const id = `converse.muc-features-${_converse.bare_jid}-${this.get('jid')}`; @@ -1559,22 +1566,6 @@ converse.plugins.add('converse-muc', { } }); }); - - function reconnectToChatRooms () { - /* Upon a reconnection event from converse, join again - * all the open groupchats. - */ - _converse.chatboxes - .filter(m => (m.get('type') === _converse.CHATROOMS_TYPE)) - .forEach(room => { - room.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - room.clearMessages(); - room.registerHandlers(); - room.fetchMessages(); - room.join(); - }); - } - _converse.api.listen.on('reconnected', reconnectToChatRooms); /************************ END Event Handlers ************************/