xmpp.chapril.org-conversejs/src/headless/converse-headlines.js
JC Brand b5eea12d49 Refactor so that message attributes are parsed early
It's better to parse an incoming message stanza early, than to have
all kinds of methods throughout the codebase that does querySelector
etc.

Firstly, it allows us to catch and report errors and malicious stanzas early on.
It also simplifies programming because you don't need to try and
remember how to properly parse a stanza, all the work is done upfront
for you.
2020-04-27 16:34:37 +02:00

163 lines
6.6 KiB
JavaScript

/**
* @module converse-headlines
* @copyright 2020, the Converse.js contributors
* @description XEP-0045 Multi-User Chat Views
*/
import { isString } from "lodash";
import { converse } from "@converse/headless/converse-core";
import st from "./utils/stanza";
converse.plugins.add('converse-headlines', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chat"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatBoxes: {
model (attrs, options) {
const { _converse } = this.__super__;
if (attrs.type == _converse.HEADLINES_TYPE) {
return new _converse.HeadlinesBox(attrs, options);
} else {
return this.__super__.model.apply(this, arguments);
}
},
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this;
const { api } = _converse;
/**
* Shows headline messages
* @class
* @namespace _converse.HeadlinesBox
* @memberOf _converse
*/
_converse.HeadlinesBox = _converse.ChatBox.extend({
defaults () {
return {
'bookmarked': false,
'hidden': ['mobile', 'fullscreen'].includes(api.settings.get("view_mode")),
'message_type': 'headline',
'num_unread': 0,
'time_opened': this.get('time_opened') || (new Date()).getTime(),
'type': _converse.HEADLINES_TYPE
}
},
initialize () {
this.initMessages();
this.set({'box_id': `box-${btoa(this.get('jid'))}`});
/**
* Triggered once a {@link _converse.HeadlinesBox} has been created and initialized.
* @event _converse#headlinesBoxInitialized
* @type { _converse.HeadlinesBox }
* @example _converse.api.listen.on('headlinesBoxInitialized', model => { ... });
*/
api.trigger('headlinesBoxInitialized', this);
}
});
async function onHeadlineMessage (stanza) {
// Handler method for all incoming messages of type "headline".
if (st.isHeadline(stanza) || st.isServerMessage(stanza)) {
const from_jid = stanza.getAttribute('from');
if (from_jid.includes('@') &&
!_converse.roster.get(from_jid) &&
!api.settings.get("allow_non_roster_messaging")) {
return;
}
if (stanza.querySelector('body') === null) {
// Avoid creating a chat box if we have nothing to show inside it.
return;
}
const chatbox = _converse.chatboxes.create({
'id': from_jid,
'jid': from_jid,
'type': _converse.HEADLINES_TYPE,
'from': from_jid
});
const attrs = await st.parseMessage(stanza, _converse);
await chatbox.createMessage(attrs);
api.trigger('message', {'chatbox': chatbox, 'stanza': stanza});
}
}
/************************ BEGIN Event Handlers ************************/
function registerHeadlineHandler () {
_converse.connection.addHandler(message => (onHeadlineMessage(message) || true), null, 'message');
}
api.listen.on('connected', registerHeadlineHandler);
api.listen.on('reconnected', registerHeadlineHandler);
/************************ END Event Handlers ************************/
/************************ BEGIN API ************************/
Object.assign(api, {
/**
* The "headlines" namespace, which is used for headline-channels
* which are read-only channels containing messages of type
* "headline".
*
* @namespace api.headlines
* @memberOf api
*/
headlines: {
/**
* Retrieves a headline-channel or all headline-channels.
*
* @method api.headlines.get
* @param {String|String[]} jids - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
* @param {Object} [attrs] - Attributes to be set on the _converse.ChatBox model.
* @param {Boolean} [create=false] - Whether the chat should be created if it's not found.
* @returns { Promise<_converse.HeadlinesBox> }
*/
async get (jids, attrs={}, create=false) {
async function _get (jid) {
let model = await api.chatboxes.get(jid);
if (!model && create) {
model = await api.chatboxes.create(jid, attrs, _converse.HeadlinesBox);
} else {
model = (model && model.get('type') === _converse.HEADLINES_TYPE) ? model : null;
if (model && Object.keys(attrs).length) {
model.save(attrs);
}
}
return model;
}
if (jids === undefined) {
const chats = await api.chatboxes.get();
return chats.filter(c => (c.get('type') === _converse.HEADLINES_TYPE));
} else if (isString(jids)) {
return _get(jids);
}
return Promise.all(jids.map(jid => _get(jid)));
}
}
});
/************************ END API ************************/
}
});