Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block users #3145

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- Remove call to `api.confirm` in `@converse/headless`
- Generate TypeScript declaration files into `dist/types`

- New config option [stanza_timeout](https://conversejs.org/docs/html/configuration.html#stanza-timeout)

## 10.1.2 (2023-02-17)

- #1490: Busy-loop when fetching registration form fails
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ po:
.PHONY: release
release:
find ./src -name "*~" -exec rm {} \;
$(SED) -i '/^_converse.VERSION_NAME =/s/=.*/= "v$(VERSION)";/' src/headless/core.js
$(SED) -i '/^export const VERSION_NAME =/s/=.*/= "v$(VERSION)";/' src/headless/shared/constants.js
$(SED) -i '/Version:/s/:.*/: $(VERSION)/' COPYRIGHT
$(SED) -i '/Project-Id-Version:/s/:.*/: Converse.js $(VERSION)\n"/' src/i18n/converse.pot
$(SED) -i '/"version":/s/:.*/: "$(VERSION)",/' manifest.json
Expand Down
7 changes: 7 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,13 @@ themselves).
In order to support all browsers we need both an MP3 and an Ogg file. Make sure
to name your files ``msg_received.ogg`` and ``msg_received.mp3``.

stanza_timeout
--------------

* Default: ``20000`` (20 seconds)

The time to wait, in milliseconds, for a response stanza (for example to an IQ
request), before a timeout error is thrown and Converse stops waiting.

sticky_controlbox
-----------------
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "10.1.2",
"description": "Browser based XMPP chat client",
"browser": "dist/converse.js",
"module": "src/converse.js",
"module": "src/index.js",
"workspaces": [
"src/headless"
],
Expand Down
2 changes: 1 addition & 1 deletion src/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const converse = {
if (settings.assets_path) {
__webpack_public_path__ = settings.assets_path; // eslint-disable-line no-undef
}
require('./converse.js');
require('./index.js');
Object.keys(plugins).forEach(name => converse.plugins.add(name, plugins[name]));
return converse;
}
Expand Down
2 changes: 1 addition & 1 deletion src/headless/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ It's also installable with NPM/Yarn as [@converse/headless](https://www.npmjs.co

The main distribution of Converse relies on the headless build.

The file [src/headless/headless.js](https://github.com/jcbrand/converse.js/blob/master/src/headless/headless.js)
The file [src/headless/index.js](https://github.com/jcbrand/converse.js/blob/master/src/headless/index.js)
is used to determine which plugins are included in the build.
1 change: 1 addition & 0 deletions src/headless/headless.js → src/headless/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import "./plugins/roster/index.js"; // RFC-6121 Contacts Roster
import "./plugins/smacks/index.js"; // XEP-0198 Stream Management
import "./plugins/status/index.js";
import "./plugins/vcard/index.js"; // XEP-0054 VCard-temp
import "./plugins/blocking/index.js"; // XEP-0191 Blocking Command
/* END: Removable components */

import { converse } from "./core.js";
Expand Down
4 changes: 1 addition & 3 deletions src/headless/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const logger = Object.assign({
* The log namespace
* @namespace log
*/
const log = {
export default {

/**
* The the log-level, which determines how verbose the logging is.
Expand Down Expand Up @@ -95,5 +95,3 @@ const log = {
this.log(message, 'fatal', style);
}
}

export default log;
2 changes: 1 addition & 1 deletion src/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"homepage": "https://conversejs.org",
"license": "MPL-2.0",
"main": "dist/converse-headless.js",
"module": "headless.js",
"module": "index.js",
"keywords": [
"converse.js",
"XMPP",
Expand Down
157 changes: 157 additions & 0 deletions src/headless/plugins/blocking/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import log from '@converse/headless/log.js';
import { _converse, api, converse } from '@converse/headless/core.js';

const { Strophe, $iq, sizzle, u } = converse.env;

export default {
/**
* Retrieves the blocklist held by the logged in user at a JID by sending an IQ stanza.
* Saves the model variable _converse.blocked.set
* @private
* @method api.refreshBlocklist
*/
async refreshBlocklist () {
const features = await api.disco.getFeatures(_converse.domain);
if (!features?.findWhere({ 'var': Strophe.NS.BLOCKING })) {
return false;
}
if (!_converse.connection) {
return false;
}

const iq = $iq({
'type': 'get',
'id': u.getUniqueId('blocklist'),
}).c('blocklist', { 'xmlns': Strophe.NS.BLOCKING });

const result = await api.sendIQ(iq).catch((e) => {
log.fatal(e);
return null;
});
if (result === null) {
const err_msg = `An error occured while fetching the blocklist`;
const { __ } = converse.env;
api.alert('error', __('Error'), err_msg);
log(err_msg, Strophe.LogLevel.WARN);
return false;
} else if (u.isErrorStanza(result)) {
log.error(`Error while fetching blocklist`);
log.error(result);
return false;
}

const blocklist = sizzle('item', result).map((item) => item.getAttribute('jid'));
_converse.blocked.set({ 'set': new Set(blocklist) });
return true;
},

/**
* Handle incoming iq stanzas in the BLOCKING namespace. Adjusts the global blocked_set.
* @private
* @method api.handleBlockingStanza
* @param { Object } [stanza] - The incoming stanza to handle
*/
handleBlockingStanza (stanza) {
if (stanza.firstElementChild.tagName === 'block') {
const users_to_block = sizzle('item', stanza).map((item) => item.getAttribute('jid'));
users_to_block.forEach(_converse.blocked.get('set').add, _converse.blocked.get('set'));
} else if (stanza.firstElementChild.tagName === 'unblock') {
const users_to_unblock = sizzle('item', stanza).map((item) => item.getAttribute('jid'));
users_to_unblock.forEach(_converse.blocked.get('set').delete, _converse.blocked.get('set'));
} else {
log.error('Received blocklist push update but could not interpret it.');
}
// TODO: Fix this to not use the length as an update key, and
// use a more accurate update method, like a length-extendable hash
_converse.blocked.set({ 'len': _converse.blocked.get('set').size });
},

/**
* Blocks JIDs by sending an IQ stanza
* @method api.blockUser
*
* @param { Array } [jid_list] - The list of JIDs to block
*/
async blockUser (jid_list) {
if (!_converse.disco_entities.get(_converse.domain)?.features?.findWhere({ 'var': Strophe.NS.BLOCKING })) {
return false;
}
if (!_converse.connection) {
return false;
}

const block_items = jid_list.map((jid) => Strophe.xmlElement('item', { 'jid': jid }));
const block_element = Strophe.xmlElement('block', { 'xmlns': Strophe.NS.BLOCKING });

block_items.forEach(block_element.appendChild, block_element);

const iq = $iq({
'type': 'set',
'id': u.getUniqueId('block'),
}).cnode(block_element);

const result = await api.sendIQ(iq).catch((e) => {
log.fatal(e);
return false;
});
const err_msg = `An error occured while trying to block user(s) ${jid_list}`;
if (result === null) {
api.alert('error', __('Error'), err_msg);
log(err_msg, Strophe.LogLevel.WARN);
return false;
} else if (u.isErrorStanza(result)) {
log.error(err_msg);
log.error(result);
return false;
}
return true;
},

/**
* Unblocks JIDs by sending an IQ stanza to the server JID specified
* @method api.unblockUser
* @param { Array } [jid_list] - The list of JIDs to unblock
*/
async unblockUser (jid_list) {
if (!_converse.disco_entities.get(_converse.domain)?.features?.findWhere({ 'var': Strophe.NS.BLOCKING })) {
return false;
}
if (!_converse.connection) {
return false;
}

const unblock_items = jid_list.map((jid) => Strophe.xmlElement('item', { 'jid': jid }));
const unblock_element = Strophe.xmlElement('unblock', { 'xmlns': Strophe.NS.BLOCKING });

unblock_items.forEach(unblock_element.append, unblock_element);

const iq = $iq({
'type': 'set',
'id': u.getUniqueId('block'),
}).cnode(unblock_element);

const result = await api.sendIQ(iq).catch((e) => {
log.fatal(e);
return false;
});
const err_msg = `An error occured while trying to unblock user(s) ${jid_list}`;
if (result === null) {
api.alert('error', __('Error'), err_msg);
log(err_msg, Strophe.LogLevel.WARN);
return false;
} else if (u.isErrorStanza(result)) {
log.error(err_msg);
log.error(result);
return false;
}
return true;
},

/**
* Retrieved the blocked set
* @method api.blockedUsers
*/
blockedUsers () {
return _converse.blocked.get('set');
},
};
36 changes: 36 additions & 0 deletions src/headless/plugins/blocking/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @description
* Converse.js plugin which adds support for XEP-0191: Blocking
* Allows users to block other users, which hides their messages.
*/
import blocking_api from './api.js';
import { _converse, api, converse } from '@converse/headless/core.js';
import { onConnected } from './utils.js';
import { Model } from '@converse/skeletor/src/model.js';

const { Strophe } = converse.env;

const SetModel = Model.extend({
defaults: {
'set': new Set(),
'len': 0,
},
});

Strophe.addNamespace('BLOCKING', 'urn:xmpp:blocking');

converse.plugins.add('converse-blocking', {
dependencies: ['converse-disco'],

enabled () {
return !api.settings.get('blacklisted_plugins').includes('converse-blocking');
},

initialize () {
_converse.blocked = new SetModel();
Object.assign(api, blocking_api);

api.listen.on('discoInitialized', onConnected);
api.listen.on('reconnected', onConnected);
},
});
8 changes: 8 additions & 0 deletions src/headless/plugins/blocking/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { _converse, api, converse } from "@converse/headless/core.js";

const { Strophe } = converse.env;

export function onConnected () {
api.refreshBlocklist();
_converse.connection.addHandler(api.handleBlockingStanza, Strophe.NS.BLOCKING, 'iq', 'set', null, null);
}
17 changes: 12 additions & 5 deletions src/headless/plugins/chat/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isObject from "lodash-es/isObject";
import log from '@converse/headless/log';
import pick from "lodash-es/pick";
import { Model } from '@converse/skeletor/src/model.js';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from "../../core.js";
import { debouncedPruneHistory, handleCorrection } from '@converse/headless/shared/chat/utils.js';
import { getMediaURLsMetadata } from '@converse/headless/shared/parsers.js';
Expand Down Expand Up @@ -368,7 +369,7 @@ const ChatBox = ModelWithContact.extend({
},

async createMessageFromError (error) {
if (error instanceof _converse.TimeoutError) {
if (error instanceof TimeoutError) {
const msg = await this.createMessage({
'type': 'error',
'message': error.message,
Expand Down Expand Up @@ -1089,17 +1090,23 @@ const ChatBox = ModelWithContact.extend({
* @param {_converse.Message} message
*/
handleUnreadMessage (message) {
if (!message?.get('body')) {
return
}
if (!message?.get('body')) return

const { pluggable } = _converse;

if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area
// gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a
// message is received.
this.ui.set('scrolled', false);
} else if (this.isHidden()) {
} else if (
this.isHidden() || (
pluggable.plugins['converse-blocking'] &&
api.blockedUsers()?.has(message?.get('from_real_jid'))
)
) {
this.incrementUnreadMsgsCounter(message);
} else {
this.sendMarkerForMessage(message);
Expand Down
5 changes: 3 additions & 2 deletions src/headless/plugins/mam/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RSM } from '@converse/headless/shared/rsm';
import log from '@converse/headless/log';
import sizzle from "sizzle";
import { RSM } from '@converse/headless/shared/rsm';
import { TimeoutError } from '../../shared/errors.js';
import { _converse, api, converse } from "@converse/headless/core";

const { Strophe, $iq, dayjs } = converse.env;
Expand Down Expand Up @@ -270,7 +271,7 @@ export default {
const { __ } = _converse;
const err_msg = __("Timeout while trying to fetch archived messages.");
log.error(err_msg);
error = new _converse.TimeoutError(err_msg);
error = new TimeoutError(err_msg);
return { messages, error };

} else if (u.isErrorStanza(iq_result)) {
Expand Down
Loading