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"
]
}],
"lodash/import-scope": "off",
"lodash/prefer-invoke-map": "off",
"lodash/prefer-startswith": "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 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

View File

@ -10,15 +10,15 @@
<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="/docs/source/_static/api.css">
<link type="text/css" rel="stylesheet" href="/css/converse.min.css">
<link rel="shortcut icon" href="/css/images/favicon.ico"/>
<link type="text/css" rel="stylesheet" href="/dist/converse.min.css">
<link rel="shortcut icon" href="/images/favicon.ico"/>
</head>
<body>
<div id="main">
<h1 class="brand-heading fade-in">
<svg
<svg
style="height: 8rem"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"

View File

@ -2,7 +2,7 @@
{% extends "!layout.html" %}
{# 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"] %}
{# 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:
* 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
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:
* https://cdn.conversejs.org/4.2.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.js
* https://cdn.conversejs.org/5.0.0/dist/converse.min.css
You can include these two URLs inside the *<head>* element of your website
via the *script* and *link* tags:
.. 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>

View File

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

View File

@ -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 ************************/
}
});

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) {
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 ************************/

View File

@ -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 ************************/

View File

@ -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 ************************/