Refactor MAM and clear private chats upon reconnection

- Add `onReconnected` method for chatboxes to clear messages
- Move MAM models to headless build.
- New event `onChatReconnected`
This commit is contained in:
JC Brand 2019-05-22 14:03:13 +02:00
parent 271c79eae8
commit 7ab59ad63e
10 changed files with 120 additions and 136 deletions

View File

@ -26,6 +26,7 @@
"split", "trim", "forEach", "toUpperCase", "includes", "values" "split", "trim", "forEach", "toUpperCase", "includes", "values"
] ]
}], }],
"lodash/import-scope": "off",
"lodash/prefer-invoke-map": "off", "lodash/prefer-invoke-map": "off",
"lodash/prefer-startswith": "off", "lodash/prefer-startswith": "off",
"lodash/prefer-constant": "off", "lodash/prefer-constant": "off",

View File

@ -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 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 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`.
- 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 - Properly handle message correction being received before the corrected message
- #1296: `embedded` view mode shows `chatbox-navback` arrow in header - #1296: `embedded` view mode shows `chatbox-navback` arrow in header
- #1465: When highlighting a roster contact, they're incorrectly shown as online - #1465: When highlighting a roster contact, they're incorrectly shown as online

View File

@ -10,8 +10,8 @@
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<link type="text/css" rel="stylesheet" href="/docs/source/_static/api.css"> <link type="text/css" rel="stylesheet" href="/docs/source/_static/api.css">
<link type="text/css" rel="stylesheet" href="/css/converse.min.css"> <link type="text/css" rel="stylesheet" href="/dist/converse.min.css">
<link rel="shortcut icon" href="/css/images/favicon.ico"/> <link rel="shortcut icon" href="/images/favicon.ico"/>
</head> </head>
<body> <body>

View File

@ -2,7 +2,7 @@
{% extends "!layout.html" %} {% extends "!layout.html" %}
{# Custom CSS overrides #} {# Custom CSS overrides #}
{% set css_files = css_files + ['_static/style.css', "../../css/converse.min.css"] %} {% set css_files = css_files + ['_static/style.css', "../../dist/converse.min.css"] %}
{% set script_files = script_files + ["../../dist/converse.min.js", "../../analytics.js"] %} {% set script_files = script_files + ["../../dist/converse.min.js", "../../analytics.js"] %}
{# Add some extra stuff before and use existing with 'super()' call. #} {# Add some extra stuff before and use existing with 'super()' call. #}

View File

@ -18,7 +18,7 @@ which hosts its JavaScript and CSS files.
The latest versions of these files are available at these URLs: The latest versions of these files are available at these URLs:
* https://cdn.conversejs.org/dist/converse.min.js * https://cdn.conversejs.org/dist/converse.min.js
* https://cdn.conversejs.org/css/converse.min.css * https://cdn.conversejs.org/dist/converse.min.css
If you are integrating Converse into an existing website or app, then we recommend If you are integrating Converse into an existing website or app, then we recommend
that you load a specific version of Converse. Otherwise your website or app that you load a specific version of Converse. Otherwise your website or app
@ -26,15 +26,15 @@ might break when a new backwards-incompatible version of Converse is released.
To load a specific version of Converse you can put the version in the URL: To load a specific version of Converse you can put the version in the URL:
* https://cdn.conversejs.org/4.2.0/dist/converse.min.js * https://cdn.conversejs.org/5.0.0/dist/converse.min.js
* https://cdn.conversejs.org/4.2.0/css/converse.min.css * https://cdn.conversejs.org/5.0.0/dist/converse.min.css
You can include these two URLs inside the *<head>* element of your website You can include these two URLs inside the *<head>* element of your website
via the *script* and *link* tags: via the *script* and *link* tags:
.. code-block:: html .. code-block:: html
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/4.2.0/css/converse.min.css"> <link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/4.2.0/dist/converse.min.css">
<script src="https://cdn.conversejs.org/4.2.0/dist/converse.min.js" charset="utf-8"></script> <script src="https://cdn.conversejs.org/4.2.0/dist/converse.min.js" charset="utf-8"></script>

View File

@ -199,7 +199,9 @@ converse.plugins.add('converse-controlbox', {
'type': _converse.CONTROLBOX_TYPE, 'type': _converse.CONTROLBOX_TYPE,
'url': '' 'url': ''
} }
} },
onReconnection: _.noop
}); });

View File

@ -7,16 +7,12 @@
// Views for XEP-0313 Message Archive Management // Views for XEP-0313 Message Archive Management
import converse from "@converse/headless/converse-core"; import converse from "@converse/headless/converse-core";
import { debounce } from 'lodash'
const CHATROOMS_TYPE = 'chatroom';
const { Strophe, _ } = converse.env;
const u = converse.env.utils;
converse.plugins.add('converse-mam-views', { 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: {
// Overrides mentioned here will be picked up by converse.js's // 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. // 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: { ChatBoxView: {
render () { render () {
const result = this.__super__.render.apply(this, arguments); const result = this.__super__.render.apply(this, arguments);
if (!this.disable_mam) { 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; 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: { ChatRoomView: {
renderChatArea () { renderChatArea () {
const result = this.__super__.renderChatArea.apply(this, arguments); const result = this.__super__.renderChatArea.apply(this, arguments);
if (!this.disable_mam) { 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; 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 ************************/
} }
}); });

View File

@ -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) { validate (attrs, options) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (!attrs.jid) { if (!attrs.jid) {
@ -1150,6 +1161,8 @@ converse.plugins.add('converse-chatboxes', {
_converse.api.trigger('chatBoxesInitialized'); _converse.api.trigger('chatBoxesInitialized');
}); });
_converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected()); _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected());
_converse.api.listen.on('reconnected', () => _converse.chatboxes.forEach(m => m.onReconnection()));
/************************ END Event Handlers ************************/ /************************ END Event Handlers ************************/

View File

@ -23,7 +23,7 @@ const MAM_ATTRIBUTES = ['with', 'start', 'end'];
converse.plugins.add('converse-mam', { converse.plugins.add('converse-mam', {
dependencies: ['converse-muc'], dependencies: ['converse-disco', 'converse-muc'],
overrides: { overrides: {
// Overrides mentioned here will be picked up by converse.js's // 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. // New functions which don't exist yet can also be added.
ChatBox: { 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) { async findDuplicateFromArchiveID (stanza) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop();
@ -66,7 +124,26 @@ converse.plugins.add('converse-mam', {
} }
return attrs; 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 () { 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)); _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.MAM));
/************************ END Event Handlers ************************/ /************************ END Event Handlers ************************/

View File

@ -240,7 +240,6 @@ converse.plugins.add('converse-muc', {
} }
}, },
async onConnectionStatusChanged () { async onConnectionStatusChanged () {
if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) { if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
this.occupants.fetchMembers(); 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 () { initFeatures () {
const storage = _converse.config.get('storage'); const storage = _converse.config.get('storage');
const id = `converse.muc-features-${_converse.bare_jid}-${this.get('jid')}`; 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 ************************/ /************************ END Event Handlers ************************/