From dc3d0a810ef90807ac912db192835f367d2f7f0b Mon Sep 17 00:00:00 2001 From: JC Brand Date: Tue, 28 Feb 2023 08:35:29 +0100 Subject: [PATCH 1/8] Experimenting with avoiding importing the `converse` global --- src/headless/log.js | 4 +-- src/headless/shared/api/public.js | 3 +- src/headless/utils/core.js | 22 ++++++------- src/plugins/modal/base.js | 20 ++++++------ src/plugins/rosterview/modals/add-contact.js | 18 +++++------ src/shared/registry.js | 2 +- src/utils/html.js | 34 ++++++++++++++------ 7 files changed, 58 insertions(+), 45 deletions(-) diff --git a/src/headless/log.js b/src/headless/log.js index 20bdfdba71..79bcaf97f4 100644 --- a/src/headless/log.js +++ b/src/headless/log.js @@ -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. @@ -95,5 +95,3 @@ const log = { this.log(message, 'fatal', style); } } - -export default log; diff --git a/src/headless/shared/api/public.js b/src/headless/shared/api/public.js index 743c287564..eb8cceb3cf 100644 --- a/src/headless/shared/api/public.js +++ b/src/headless/shared/api/public.js @@ -13,6 +13,7 @@ import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe'; import { html } from 'lit'; import { initAppSettings } from '../settings/utils.js'; import { sprintf } from 'sprintf-js'; +import { stx } from '../../utils/stanza.js'; import { cleanup, @@ -199,7 +200,7 @@ export const converse = Object.assign(window.converse || {}, { log, sizzle, sprintf, - stx: u.stx, + stx, u, } }); diff --git a/src/headless/utils/core.js b/src/headless/utils/core.js index ae4b8a591f..9664b74c5c 100644 --- a/src/headless/utils/core.js +++ b/src/headless/utils/core.js @@ -8,14 +8,20 @@ import _converse from '@converse/headless/shared/_converse.js'; import compact from "lodash-es/compact"; import isObject from "lodash-es/isObject"; import last from "lodash-es/last"; -import log from '@converse/headless/log.js'; +import log from '../log.js'; import sizzle from "sizzle"; import { Model } from '@converse/skeletor/src/model.js'; import { Strophe } from 'strophe.js/src/strophe.js'; import { getOpenPromise } from '@converse/openpromise'; -import { settings_api } from '@converse/headless/shared/settings/api.js'; +import { settings_api } from '../shared/settings/api.js'; import { stx , toStanza } from './stanza.js'; +/** + * The utils object + * @namespace u + */ +const u = {}; + export function isElement (el) { return el instanceof Element || el instanceof HTMLDocument; } @@ -57,14 +63,15 @@ export function shouldClearCache () { export async function tearDown () { - await _converse.api.trigger('beforeTearDown', {'synchronous': true}); + const { api } = _converse; + await api.trigger('beforeTearDown', {'synchronous': true}); window.removeEventListener('click', _converse.onUserActivity); window.removeEventListener('focus', _converse.onUserActivity); window.removeEventListener('keypress', _converse.onUserActivity); window.removeEventListener('mousemove', _converse.onUserActivity); window.removeEventListener(_converse.unloadevent, _converse.onUserActivity); window.clearInterval(_converse.everySecondTrigger); - _converse.api.trigger('afterTearDown'); + api.trigger('afterTearDown'); return _converse; } @@ -97,13 +104,6 @@ export function prefixMentions (message) { return text; } - -/** - * The utils object - * @namespace u - */ -const u = {}; - u.isTagEqual = function (stanza, name) { if (stanza.tree?.()) { return u.isTagEqual(stanza.tree(), name); diff --git a/src/plugins/modal/base.js b/src/plugins/modal/base.js index 2741e7ae09..0b9648aa25 100644 --- a/src/plugins/modal/base.js +++ b/src/plugins/modal/base.js @@ -1,14 +1,14 @@ +import api from "@converse/headless/shared/api/index.js"; import bootstrap from "bootstrap.native"; import log from "@converse/headless/log"; +import sizzle from 'sizzle'; import tplAlertComponent from "./templates/modal-alert.js"; import { View } from '@converse/skeletor/src/view.js'; -import { api, converse } from "@converse/headless/core"; +import { addClass, removeElement } from '../../utils/html.js'; import { render } from 'lit'; import './styles/_modal.scss'; -const { sizzle } = converse.env; -const u = converse.env.utils; const BaseModal = View.extend({ @@ -56,11 +56,11 @@ const BaseModal = View.extend({ ev.stopPropagation(); ev.preventDefault(); sizzle('.nav-link.active', this.el).forEach(el => { - u.removeClass('active', this.el.querySelector(el.getAttribute('href'))); - u.removeClass('active', el); + removeClass('active', this.el.querySelector(el.getAttribute('href'))); + removeClass('active', el); }); - u.addClass('active', ev.target); - u.addClass('active', this.el.querySelector(ev.target.getAttribute('href'))) + addClass('active', ev.target); + addClass('active', this.el.querySelector(ev.target.getAttribute('href'))) }, alert (message, type='primary') { @@ -74,8 +74,8 @@ const BaseModal = View.extend({ render(tplAlertComponent({'type': `alert-${type}`, 'message': message}), body); const el = body.firstElementChild; setTimeout(() => { - u.addClass('fade-out', el); - setTimeout(() => u.removeElement(el), 600); + addClass('fade-out', el); + setTimeout(() => removeElement(el), 600); }, 5000); }, @@ -83,7 +83,7 @@ const BaseModal = View.extend({ if (ev) { ev.preventDefault(); this.trigger_el = ev.target; - !u.hasClass('chat-image', this.trigger_el) && u.addClass('selected', this.trigger_el); + !hasClass('chat-image', this.trigger_el) && addClass('selected', this.trigger_el); } this.modal.show(); } diff --git a/src/plugins/rosterview/modals/add-contact.js b/src/plugins/rosterview/modals/add-contact.js index 268f6902c4..dcfff9919f 100644 --- a/src/plugins/rosterview/modals/add-contact.js +++ b/src/plugins/rosterview/modals/add-contact.js @@ -1,13 +1,13 @@ import 'shared/autocomplete/index.js'; import BaseModal from "plugins/modal/modal.js"; +import api from '@converse/headless/shared/api'; import compact from 'lodash-es/compact'; import debounce from 'lodash-es/debounce'; import tplAddContactModal from "./templates/add-contact.js"; +import { Strophe } from 'strophe.js/src/core.js'; import { __ } from 'i18n'; -import { _converse, api, converse } from "@converse/headless/core"; - -const { Strophe } = converse.env; -const u = converse.env.utils; +import { _converse } from "@converse/headless/core"; +import { addClass, removeClass } from 'utils/html.js'; export default class AddContactModal extends BaseModal { @@ -98,7 +98,7 @@ export default class AddContactModal extends BaseModal { if (list.length !== 1) { const el = this.querySelector('.invalid-feedback'); el.textContent = __('Sorry, could not find a contact with that name') - u.addClass('d-block', el); + addClass('d-block', el); return; } const jid = list[0].value; @@ -114,15 +114,15 @@ export default class AddContactModal extends BaseModal { validateSubmission (jid) { const el = this.querySelector('.invalid-feedback'); if (!jid || compact(jid.split('@')).length < 2) { - u.addClass('is-invalid', this.querySelector('input[name="jid"]')); - u.addClass('d-block', el); + addClass('is-invalid', this.querySelector('input[name="jid"]')); + addClass('d-block', el); return false; } else if (_converse.roster.get(Strophe.getBareJidFromJid(jid))) { el.textContent = __('This contact has already been added') - u.addClass('d-block', el); + addClass('d-block', el); return false; } - u.removeClass('d-block', el); + removeClass('d-block', el); return true; } diff --git a/src/shared/registry.js b/src/shared/registry.js index 3feb7b7b5e..c1888daa02 100644 --- a/src/shared/registry.js +++ b/src/shared/registry.js @@ -1,4 +1,4 @@ -import { api } from "@converse/headless/core"; +import api from "@converse/headless/shared/api/index.js"; const registry = {}; diff --git a/src/utils/html.js b/src/utils/html.js index 0d2fb192a8..fedfa41ad3 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -241,7 +241,7 @@ u.hasClass = function (className, el) { }; u.toggleClass = function (className, el) { - u.hasClass(className, el) ? u.removeClass(className, el) : u.addClass(className, el); + u.hasClass(className, el) ? removeClass(className, el) : addClass(className, el); }; /** @@ -250,10 +250,10 @@ u.toggleClass = function (className, el) { * @param { string } className * @param { Element } el */ -u.addClass = function (className, el) { +export function addClass (className, el) { el instanceof Element && el.classList.add(className); return el; -}; +} /** * Remove a class from an element. @@ -261,15 +261,20 @@ u.addClass = function (className, el) { * @param { string } className * @param { Element } el */ -u.removeClass = function (className, el) { +export function removeClass (className, el) { el instanceof Element && el.classList.remove(className); return el; -}; +} -u.removeElement = function (el) { +/** + * Remove an element from its parent + * @method u#removeElement + * @param { Element } el + */ +export function removeElement (el) { el instanceof Element && el.parentNode && el.parentNode.removeChild(el); return el; -}; +} u.getElementFromTemplateResult = function (tr) { const div = document.createElement('div'); @@ -278,8 +283,8 @@ u.getElementFromTemplateResult = function (tr) { }; u.showElement = el => { - u.removeClass('collapsed', el); - u.removeClass('hidden', el); + removeClass('collapsed', el); + removeClass('hidden', el); }; u.hideElement = function (el) { @@ -609,6 +614,15 @@ u.xForm2TemplateResult = function (field, stanza, options={}) { } }; -Object.assign(u, { getOOBURLMarkup, ancestor, slideIn, slideOut, isEqualNode }); +Object.assign(u, { + addClass, + ancestor, + getOOBURLMarkup, + isEqualNode, + removeClass, + removeElement, + slideIn, + slideOut, +}); export default u; From b4118fa9017a5678834add5c5ce68e9139c757c7 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 2 Mar 2023 09:08:22 +0100 Subject: [PATCH 2/8] Move CONNECTION_STATUS to constant.js --- src/headless/shared/_converse.js | 4 ++-- src/headless/shared/connection/index.js | 5 +++-- src/plugins/controlbox/templates/loginform.js | 3 ++- src/plugins/register/index.js | 9 +++++---- src/plugins/register/panel.js | 3 ++- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/headless/shared/_converse.js b/src/headless/shared/_converse.js index 279316f6d1..eedec966af 100644 --- a/src/headless/shared/_converse.js +++ b/src/headless/shared/_converse.js @@ -19,9 +19,9 @@ import { shouldClearCache } from '../utils/core.js'; */ const _converse = { log, - shouldClearCache, // TODO: Should be moved to utils with next major release - CONNECTION_STATUS, + shouldClearCache, // TODO: Should be moved to utils with next major release + CONNECTION_STATUS, // TODO: remove in next major release VERSION_NAME, templates: {}, diff --git a/src/headless/shared/connection/index.js b/src/headless/shared/connection/index.js index 231657e3e5..7ba59b9d8b 100644 --- a/src/headless/shared/connection/index.js +++ b/src/headless/shared/connection/index.js @@ -2,10 +2,11 @@ import debounce from 'lodash-es/debounce'; import log from "../../log.js"; import sizzle from 'sizzle'; import { BOSH_WAIT } from '../../shared/constants.js'; +import { CONNECTION_STATUS } from '../constants'; import { Strophe } from 'strophe.js/src/core.js'; import { _converse, api } from "../../core.js"; -import { getOpenPromise } from '@converse/openpromise'; import { clearSession, tearDown } from "../../utils/core.js"; +import { getOpenPromise } from '@converse/openpromise'; import { setUserJID, } from '../../utils/init.js'; const i = Object.keys(Strophe.Status).reduce((max, k) => Math.max(max, Strophe.Status[k]), 0); @@ -308,7 +309,7 @@ export class Connection extends Strophe.Connection { */ onConnectStatusChanged (status, message) { const { __ } = _converse; - log.debug(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`); + log.debug(`Status changed to: ${CONNECTION_STATUS[status]}`); if (status === Strophe.Status.ATTACHFAIL) { this.setConnectionStatus(status); this.worker_attach_promise?.resolve(false); diff --git a/src/plugins/controlbox/templates/loginform.js b/src/plugins/controlbox/templates/loginform.js index 92de74c422..17eaaa85c3 100644 --- a/src/plugins/controlbox/templates/loginform.js +++ b/src/plugins/controlbox/templates/loginform.js @@ -1,5 +1,6 @@ import 'shared/components/brand-heading.js'; import tplSpinner from 'templates/spinner.js'; +import { CONNECTION_STATUS } from '@converse/headless/shared/constants'; import { REPORTABLE_STATUSES, PRETTY_CONNECTION_STATUS, CONNECTION_STATUS_CSS_CLASS } from '../constants.js'; import { __ } from 'i18n'; import { _converse, api } from "@converse/headless/core"; @@ -139,6 +140,6 @@ export default (el) => { - ${ (_converse.CONNECTION_STATUS[connection_status] === 'CONNECTING') ? tplSpinner({'classes': 'hor_centered'}) : form_fields(el) } + ${ (CONNECTION_STATUS[connection_status] === 'CONNECTING') ? tplSpinner({'classes': 'hor_centered'}) : form_fields(el) } `; } diff --git a/src/plugins/register/index.js b/src/plugins/register/index.js index 28e845eaa3..89e046d1cf 100644 --- a/src/plugins/register/index.js +++ b/src/plugins/register/index.js @@ -10,6 +10,7 @@ import './panel.js'; import { __ } from 'i18n'; import { _converse, api, converse } from '@converse/headless/core'; import { setActiveForm } from './utils.js'; +import { CONNECTION_STATUS } from '@converse/headless/shared/constants'; // Strophe methods for building stanzas const { Strophe } = converse.env; @@ -35,10 +36,10 @@ converse.plugins.add('converse-register', { initialize () { const { router } = _converse; - _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL'; - _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED'; - _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT'; - _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; + CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL'; + CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED'; + CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT'; + CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; api.settings.extend({ 'allow_registration': true, diff --git a/src/plugins/register/panel.js b/src/plugins/register/panel.js index 2ed6b31702..c05e27a84f 100644 --- a/src/plugins/register/panel.js +++ b/src/plugins/register/panel.js @@ -3,6 +3,7 @@ import tplFormInput from "templates/form_input.js"; import tplFormUrl from "templates/form_url.js"; import tplFormUsername from "templates/form_username.js"; import tplRegisterPanel from "./templates/register_panel.js"; +import { CONNECTION_STATUS } from '@converse/headless/shared/constants'; import { CustomElement } from 'shared/components/element.js'; import { __ } from 'i18n'; import { _converse, api, converse } from "@converse/headless/core.js"; @@ -219,7 +220,7 @@ class RegisterPanel extends CustomElement { ].includes(status_code)) { log.error( - `Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}` + `Problem during registration: Strophe.Status is ${CONNECTION_STATUS[status_code]}` ); this.abortRegistration(); } else if (status_code === Strophe.Status.REGISTERED) { From 71322a37e816af375872f8c8df7f3f1336732700 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 2 Mar 2023 09:25:28 +0100 Subject: [PATCH 3/8] Create new config setting `stanza_timeout` And move STANZA_TIMEOUT off `_converse` and into constants.js --- CHANGES.md | 2 ++ docs/source/configuration.rst | 7 +++++++ src/headless/plugins/muc/muc.js | 7 ++++--- src/headless/plugins/roster/utils.js | 5 +++-- src/headless/plugins/status/api.js | 5 +++-- src/headless/shared/_converse.js | 18 +----------------- src/headless/shared/api/public.js | 3 ++- src/headless/shared/api/send.js | 9 +++++---- src/headless/shared/constants.js | 18 ++++++++++++++---- src/headless/shared/settings/constants.js | 1 + src/plugins/muc-views/tests/retractions.js | 4 +--- 11 files changed, 43 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ff9ab09498..b0ec7be097 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index d2ee6d14a8..29f20988b7 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -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 ----------------- diff --git a/src/headless/plugins/muc/muc.js b/src/headless/plugins/muc/muc.js index a9ea9551a5..2855fe7501 100644 --- a/src/headless/plugins/muc/muc.js +++ b/src/headless/plugins/muc/muc.js @@ -5,17 +5,17 @@ import p from '../../utils/parse-helpers'; import pick from 'lodash-es/pick'; import sizzle from 'sizzle'; import { Model } from '@converse/skeletor/src/model.js'; +import { ROOMSTATUS } from './constants.js'; import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe'; import { _converse, api, converse } from '../../core.js'; import { computeAffiliationsDelta, setAffiliations, getAffiliationList } from './affiliations/utils.js'; -import { handleCorrection } from '../../shared/chat/utils.js'; import { getOpenPromise } from '@converse/openpromise'; +import { handleCorrection } from '../../shared/chat/utils.js'; import { initStorage } from '../../utils/storage.js'; import { isArchived, getMediaURLsMetadata } from '../../shared/parsers.js'; import { isUniView, getUniqueId, safeSave } from '../../utils/core.js'; import { parseMUCMessage, parseMUCPresence } from './parsers.js'; import { sendMarker } from '../../shared/actions.js'; -import { ROOMSTATUS } from './constants.js'; const { u } = converse.env; @@ -723,7 +723,8 @@ const ChatRoomMixin = { el.setAttribute('id', id); } const promise = getOpenPromise(); - const timeoutHandler = _converse.connection.addTimedHandler(_converse.STANZA_TIMEOUT, () => { + const timeout = api.settings.get('stanza_timeout'); + const timeoutHandler = _converse.connection.addTimedHandler(timeout, () => { _converse.connection.deleteHandler(handler); const err = new _converse.TimeoutError('Timeout Error: No response from server'); promise.resolve(err); diff --git a/src/headless/plugins/roster/utils.js b/src/headless/plugins/roster/utils.js index d7ceca8ec3..094a46389f 100644 --- a/src/headless/plugins/roster/utils.js +++ b/src/headless/plugins/roster/utils.js @@ -1,6 +1,7 @@ import log from "@converse/headless/log"; import { Model } from '@converse/skeletor/src/model.js'; import { RosterFilter } from '@converse/headless/plugins/roster/filter.js'; +import { STATUS_WEIGHTS } from "../../shared/constants"; import { _converse, api, converse } from "@converse/headless/core"; import { initStorage } from '@converse/headless/utils/storage.js'; import { shouldClearCache } from '@converse/headless/utils/core.js'; @@ -198,12 +199,12 @@ export function rejectPresenceSubscription (jid, message) { export function contactsComparator (contact1, contact2) { const status1 = contact1.presence.get('show') || 'offline'; const status2 = contact2.presence.get('show') || 'offline'; - if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) { + if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) { const name1 = (contact1.getDisplayName()).toLowerCase(); const name2 = (contact2.getDisplayName()).toLowerCase(); return name1 < name2 ? -1 : (name1 > name2? 1 : 0); } else { - return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1; + return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1; } } diff --git a/src/headless/plugins/status/api.js b/src/headless/plugins/status/api.js index 63379e8a2a..e689538bf6 100644 --- a/src/headless/plugins/status/api.js +++ b/src/headless/plugins/status/api.js @@ -1,4 +1,5 @@ -import { _converse, api } from '@converse/headless/core'; +import { _converse, api } from '../../core'; +import { STATUS_WEIGHTS } from '../../shared/constants'; export default { @@ -32,7 +33,7 @@ export default { */ async set (value, message) { const data = {'status': value}; - if (!Object.keys(_converse.STATUS_WEIGHTS).includes(value)) { + if (!Object.keys(STATUS_WEIGHTS).includes(value)) { throw new Error( 'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1' ); diff --git a/src/headless/shared/_converse.js b/src/headless/shared/_converse.js index eedec966af..31c1cf4e6e 100644 --- a/src/headless/shared/_converse.js +++ b/src/headless/shared/_converse.js @@ -1,7 +1,7 @@ import i18n from './i18n.js'; import log from '../log.js'; import pluggable from 'pluggable.js/src/pluggable.js'; -import { CONNECTION_STATUS, VERSION_NAME } from './constants'; +import { VERSION_NAME } from './constants'; import { Events } from '@converse/skeletor/src/events.js'; import { Router } from '@converse/skeletor/src/router.js'; import { TimeoutError } from './errors.js'; @@ -21,7 +21,6 @@ const _converse = { log, shouldClearCache, // TODO: Should be moved to utils with next major release - CONNECTION_STATUS, // TODO: remove in next major release VERSION_NAME, templates: {}, @@ -29,15 +28,6 @@ const _converse = { 'initialized': getOpenPromise() }, - STATUS_WEIGHTS: { - 'offline': 6, - 'unavailable': 5, - 'xa': 4, - 'away': 3, - 'dnd': 2, - 'chat': 1, // We currently don't differentiate between "chat" and "online" - 'online': 1 - }, ANONYMOUS: 'anonymous', CLOSED: 'closed', EXTERNAL: 'external', @@ -46,12 +36,6 @@ const _converse = { OPENED: 'opened', PREBIND: 'prebind', - /** - * @constant - * @type { number } - */ - STANZA_TIMEOUT: 20000, - SUCCESS: 'success', FAILURE: 'failure', diff --git a/src/headless/shared/api/public.js b/src/headless/shared/api/public.js index eb8cceb3cf..532935ab8b 100644 --- a/src/headless/shared/api/public.js +++ b/src/headless/shared/api/public.js @@ -6,7 +6,7 @@ import i18n from '../i18n'; import log from '../../log.js'; import sizzle from 'sizzle'; import u, { setUnloadEvent } from '../../utils/core.js'; -import { CHAT_STATES, KEYCODES } from '../constants.js'; +import { CHAT_STATES, KEYCODES, VERSION_NAME } from '../constants.js'; import { Collection } from "@converse/skeletor/src/collection"; import { Model } from '@converse/skeletor/src/model.js'; import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe'; @@ -185,6 +185,7 @@ export const converse = Object.assign(window.converse || {}, { * @memberOf converse */ 'env': { + VERSION_NAME, $build, $iq, $msg, diff --git a/src/headless/shared/api/send.js b/src/headless/shared/api/send.js index 27b3ae4a0d..a6675918cf 100644 --- a/src/headless/shared/api/send.js +++ b/src/headless/shared/api/send.js @@ -1,4 +1,4 @@ -import _converse from '@converse/headless/shared/_converse.js'; +import _converse from '../../shared/_converse.js'; import log from '../../log.js'; import { Strophe } from 'strophe.js/src/strophe'; import { TimeoutError } from '../errors.js'; @@ -43,7 +43,8 @@ export default { * Send an IQ stanza * @method _converse.api.sendIQ * @param { Element } stanza - * @param { number } [timeout=_converse.STANZA_TIMEOUT] + * @param { number } [timeout] - The default timeout value is taken from + * the `stanza_timeout` configuration setting. * @param { Boolean } [reject=true] - Whether an error IQ should cause the promise * to be rejected. If `false`, the promise will resolve instead of being rejected. * @returns { Promise } A promise which resolves (or potentially rejected) once we @@ -51,13 +52,13 @@ export default { * If the IQ stanza being sent is of type `result` or `error`, there's * nothing to wait for, so an already resolved promise is returned. */ - sendIQ (stanza, timeout=_converse.STANZA_TIMEOUT, reject=true) { + sendIQ (stanza, timeout, reject=true) { const { api, connection } = _converse; let promise; stanza = stanza.tree?.() ?? stanza; if (['get', 'set'].includes(stanza.getAttribute('type'))) { - timeout = timeout || _converse.STANZA_TIMEOUT; + timeout = timeout || api.settings.get('stanza_timeout'); if (reject) { promise = new Promise((resolve, reject) => connection.sendIQ(stanza, resolve, reject, timeout)); promise.catch((e) => { diff --git a/src/headless/shared/constants.js b/src/headless/shared/constants.js index b02270e5cc..9c0686f736 100644 --- a/src/headless/shared/constants.js +++ b/src/headless/shared/constants.js @@ -1,7 +1,17 @@ import { Strophe } from 'strophe.js/src/strophe'; export const BOSH_WAIT = 59; -export const VERSION_NAME = "v10.1.2"; +export const VERSION_NAME = 'v10.1.2'; + +export const STATUS_WEIGHTS = { + offline: 6, + unavailable: 5, + xa: 4, + away: 3, + dnd: 2, + chat: 1, // We don't differentiate between "chat" and "online" + online: 1, +}; export const CONNECTION_STATUS = {}; CONNECTION_STATUS[Strophe.Status.ATTACHED] = 'ATTACHED'; @@ -73,7 +83,7 @@ export const CORE_PLUGINS = [ 'converse-roster', 'converse-smacks', 'converse-status', - 'converse-vcard' + 'converse-vcard', ]; export const URL_PARSE_OPTIONS = { 'start': /(\b|_)(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi }; @@ -94,5 +104,5 @@ export const KEYCODES = { FORWARD_SLASH: 47, AT: 50, META: 91, - META_RIGHT: 93 -} + META_RIGHT: 93, +}; diff --git a/src/headless/shared/settings/constants.js b/src/headless/shared/settings/constants.js index 29d6f38667..e8738888f2 100644 --- a/src/headless/shared/settings/constants.js +++ b/src/headless/shared/settings/constants.js @@ -101,6 +101,7 @@ export const DEFAULT_SETTINGS = { sid: undefined, singleton: false, strict_plugin_dependencies: false, + stanza_timeout: 20000, view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile' websocket_url: undefined, whitelisted_plugins: [], diff --git a/src/plugins/muc-views/tests/retractions.js b/src/plugins/muc-views/tests/retractions.js index 6faf82ef6a..fda19a7328 100644 --- a/src/plugins/muc-views/tests/retractions.js +++ b/src/plugins/muc-views/tests/retractions.js @@ -661,9 +661,7 @@ describe("Message Retractions", function () { })); it("can be retracted by its author, causing a timeout error in response", - mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - - _converse.STANZA_TIMEOUT = 1; + mock.initConverse(['chatBoxesFetched'], { stanza_timeout: 1 }, async function (_converse) { const muc_jid = 'lounge@montague.lit'; const features = [...mock.default_muc_features, Strophe.NS.MODERATE]; From dc3172a54d2bac765bb05b9a42420b9981408d2b Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 2 Mar 2023 12:18:26 +0100 Subject: [PATCH 4/8] Start work on removing constants from _converse object --- Makefile | 2 +- src/headless/plugins/chat/model.js | 3 +- src/headless/plugins/mam/api.js | 5 +- src/headless/plugins/muc/muc.js | 9 +- src/headless/shared/_converse.js | 86 +++++++----- src/headless/shared/api/public.js | 33 ++--- src/headless/shared/api/user.js | 5 +- src/headless/shared/connection/index.js | 12 +- src/headless/shared/constants.js | 29 +++++ src/headless/shared/errors.js | 2 +- src/headless/utils/init.js | 14 +- src/plugins/controlbox/controlbox.js | 3 +- src/plugins/controlbox/loginform.js | 3 +- src/plugins/controlbox/templates/loginform.js | 122 ++++++++++-------- 14 files changed, 201 insertions(+), 127 deletions(-) diff --git a/Makefile b/Makefile index 178e3b60c7..780857c108 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/headless/plugins/chat/model.js b/src/headless/plugins/chat/model.js index cbdff51f18..96508ee7fb 100644 --- a/src/headless/plugins/chat/model.js +++ b/src/headless/plugins/chat/model.js @@ -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'; @@ -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, diff --git a/src/headless/plugins/mam/api.js b/src/headless/plugins/mam/api.js index e07a804cbe..0c828d860a 100644 --- a/src/headless/plugins/mam/api.js +++ b/src/headless/plugins/mam/api.js @@ -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; @@ -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)) { diff --git a/src/headless/plugins/muc/muc.js b/src/headless/plugins/muc/muc.js index 2855fe7501..8d41025d70 100644 --- a/src/headless/plugins/muc/muc.js +++ b/src/headless/plugins/muc/muc.js @@ -7,6 +7,7 @@ import sizzle from 'sizzle'; import { Model } from '@converse/skeletor/src/model.js'; import { ROOMSTATUS } from './constants.js'; import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe'; +import { TimeoutError } from '../../shared/errors.js'; import { _converse, api, converse } from '../../core.js'; import { computeAffiliationsDelta, setAffiliations, getAffiliationList } from './affiliations/utils.js'; import { getOpenPromise } from '@converse/openpromise'; @@ -709,8 +710,8 @@ const ChatRoomMixin = { * @private * @method _converse.ChatRoom#sendTimedMessage * @param { _converse.Message|Element } message - * @returns { Promise|Promise<_converse.TimeoutError> } Returns a promise - * which resolves with the reflected message stanza or with an error stanza or {@link _converse.TimeoutError}. + * @returns { Promise|Promise } Returns a promise + * which resolves with the reflected message stanza or with an error stanza or {@link TimeoutError}. */ sendTimedMessage (el) { if (typeof el.tree === 'function') { @@ -726,7 +727,7 @@ const ChatRoomMixin = { const timeout = api.settings.get('stanza_timeout'); const timeoutHandler = _converse.connection.addTimedHandler(timeout, () => { _converse.connection.deleteHandler(handler); - const err = new _converse.TimeoutError('Timeout Error: No response from server'); + const err = new TimeoutError('Timeout Error: No response from server'); promise.resolve(err); return false; }); @@ -776,7 +777,7 @@ const ChatRoomMixin = { if (u.isErrorStanza(result)) { log.error(result); - } else if (result instanceof _converse.TimeoutError) { + } else if (result instanceof TimeoutError) { log.error(result); message.save({ editable, diff --git a/src/headless/shared/_converse.js b/src/headless/shared/_converse.js index 31c1cf4e6e..3052148f6f 100644 --- a/src/headless/shared/_converse.js +++ b/src/headless/shared/_converse.js @@ -1,15 +1,37 @@ import i18n from './i18n.js'; import log from '../log.js'; import pluggable from 'pluggable.js/src/pluggable.js'; -import { VERSION_NAME } from './constants'; import { Events } from '@converse/skeletor/src/events.js'; import { Router } from '@converse/skeletor/src/router.js'; -import { TimeoutError } from './errors.js'; import { createStore, getDefaultStore } from '../utils/storage.js'; import { getInitSettings } from './settings/utils.js'; import { getOpenPromise } from '@converse/openpromise'; import { shouldClearCache } from '../utils/core.js'; +import { + ACTIVE, + ANONYMOUS, + CHATROOMS_TYPE, + CLOSED, + COMPOSING, + CONTROLBOX_TYPE, + DEFAULT_IMAGE, + DEFAULT_IMAGE_TYPE, + EXTERNAL, + FAILURE, + GONE, + HEADLINES_TYPE, + INACTIVE, + LOGIN, + LOGOUT, + OPENED, + PAUSED, + PREBIND, + PRIVATE_CHAT_TYPE, + SUCCESS, + VERSION_NAME +} from './constants'; + /** * A private, closured object containing the private api (via {@link _converse.api}) @@ -28,46 +50,42 @@ const _converse = { 'initialized': getOpenPromise() }, - ANONYMOUS: 'anonymous', - CLOSED: 'closed', - EXTERNAL: 'external', - LOGIN: 'login', - LOGOUT: 'logout', - OPENED: 'opened', - PREBIND: 'prebind', - - SUCCESS: 'success', - FAILURE: 'failure', - - // Generated from css/images/user.svg - DEFAULT_IMAGE_TYPE: 'image/svg+xml', - DEFAULT_IMAGE: "PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCI+CiA8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgZmlsbD0iIzU1NSIvPgogPGNpcmNsZSBjeD0iNjQiIGN5PSI0MSIgcj0iMjQiIGZpbGw9IiNmZmYiLz4KIDxwYXRoIGQ9Im0yOC41IDExMiB2LTEyIGMwLTEyIDEwLTI0IDI0LTI0IGgyMyBjMTQgMCAyNCAxMiAyNCAyNCB2MTIiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==", - + // TODO: remove constants in next major release + ANONYMOUS, + CLOSED, + EXTERNAL, + LOGIN, + LOGOUT, + OPENED, + PREBIND, + + SUCCESS, + FAILURE, + + DEFAULT_IMAGE_TYPE, + DEFAULT_IMAGE, + + INACTIVE, + ACTIVE, + COMPOSING, + PAUSED, + GONE, + + PRIVATE_CHAT_TYPE, + CHATROOMS_TYPE, + HEADLINES_TYPE, + CONTROLBOX_TYPE, + + // Set as module attr so that we can override in tests. + // TODO: replace with config settings TIMEOUTS: { - // Set as module attr so that we can override in tests. PAUSED: 10000, INACTIVE: 90000 }, - // XEP-0085 Chat states - // https://xmpp.org/extensions/xep-0085.html - INACTIVE: 'inactive', - ACTIVE: 'active', - COMPOSING: 'composing', - PAUSED: 'paused', - GONE: 'gone', - - // Chat types - PRIVATE_CHAT_TYPE: 'chatbox', - CHATROOMS_TYPE: 'chatroom', - HEADLINES_TYPE: 'headline', - CONTROLBOX_TYPE: 'controlbox', - default_connection_options: {'explicitResourceBinding': true}, router: new Router(), - TimeoutError: TimeoutError, - isTestEnv: () => { return getInitSettings()['bosh_service_url'] === 'montague.lit/http-bind'; }, diff --git a/src/headless/shared/api/public.js b/src/headless/shared/api/public.js index 532935ab8b..adeed037e4 100644 --- a/src/headless/shared/api/public.js +++ b/src/headless/shared/api/public.js @@ -6,10 +6,11 @@ import i18n from '../i18n'; import log from '../../log.js'; import sizzle from 'sizzle'; import u, { setUnloadEvent } from '../../utils/core.js'; -import { CHAT_STATES, KEYCODES, VERSION_NAME } from '../constants.js'; +import { ANONYMOUS, CHAT_STATES, KEYCODES, VERSION_NAME } from '../constants.js'; import { Collection } from "@converse/skeletor/src/collection"; import { Model } from '@converse/skeletor/src/model.js'; import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe'; +import { TimeoutError } from '../errors.js'; import { html } from 'lit'; import { initAppSettings } from '../settings/utils.js'; import { sprintf } from 'sprintf-js'; @@ -71,7 +72,7 @@ export const converse = Object.assign(window.converse || {}, { _converse.strict_plugin_dependencies = settings.strict_plugin_dependencies; // Needed by pluggable.js log.setLogLevel(api.settings.get("loglevel")); - if (api.settings.get("authentication") === _converse.ANONYMOUS) { + if (api.settings.get("authentication") === ANONYMOUS) { if (api.settings.get("auto_login") && !api.settings.get('jid')) { throw new Error("Config Error: you need to provide the server's " + "domain via the 'jid' option when using anonymous " + @@ -170,22 +171,22 @@ export const converse = Object.assign(window.converse || {}, { /** * Utility methods and globals from bundled 3rd party libraries. * @typedef ConverseEnv - * @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects. - * @property {function} converse.env.$iq - Creates a Strophe.Builder with an element as the root. - * @property {function} converse.env.$msg - Creates a Strophe.Builder with an element as the root. - * @property {function} converse.env.$pres - Creates a Strophe.Builder with an element as the root. - * @property {function} converse.env.Promise - The Promise implementation used by Converse. - * @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse. - * @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods. - * @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine. - * @property {function} converse.env.sprintf - * @property {object} converse.env._ - The instance of [lodash-es](http://lodash.com) used by Converse. - * @property {object} converse.env.dayjs - [DayJS](https://github.com/iamkun/dayjs) date manipulation library. - * @property {object} converse.env.utils - Module containing common utility methods used by Converse. + * @property { Error } converse.env.TimeoutError + * @property { function } converse.env.$build - Creates a Strophe.Builder, for creating stanza objects. + * @property { function } converse.env.$iq - Creates a Strophe.Builder with an element as the root. + * @property { function } converse.env.$msg - Creates a Strophe.Builder with an element as the root. + * @property { function } converse.env.$pres - Creates a Strophe.Builder with an element as the root. + * @property { function } converse.env.Promise - The Promise implementation used by Converse. + * @property { function } converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse. + * @property { function } converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods. + * @property { function } converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine. + * @property { function } converse.env.sprintf + * @property { object } converse.env._ - The instance of [lodash-es](http://lodash.com) used by Converse. + * @property { object } converse.env.dayjs - [DayJS](https://github.com/iamkun/dayjs) date manipulation library. + * @property { object } converse.env.utils - Module containing common utility methods used by Converse. * @memberOf converse */ 'env': { - VERSION_NAME, $build, $iq, $msg, @@ -195,7 +196,9 @@ export const converse = Object.assign(window.converse || {}, { Model, Promise, Strophe, + TimeoutError, URI, + VERSION_NAME, dayjs, html, log, diff --git a/src/headless/shared/api/user.js b/src/headless/shared/api/user.js index 711478a708..49ed271bef 100644 --- a/src/headless/shared/api/user.js +++ b/src/headless/shared/api/user.js @@ -4,6 +4,7 @@ import u, { replacePromise } from '../../utils/core.js'; import { attemptNonPreboundSession, initConnection, setUserJID } from '../../utils/init.js'; import { getOpenPromise } from '@converse/openpromise'; import { user_settings_api } from '../settings/api.js'; +import { LOGOUT, PREBIND } from '../constants.js'; export default { /** @@ -60,7 +61,7 @@ export default { if (bosh_plugin?.enabled()) { if (await _converse.restoreBOSHSession()) { return; - } else if (api.settings.get("authentication") === _converse.PREBIND && (!automatic || api.settings.get("auto_login"))) { + } else if (api.settings.get("authentication") === PREBIND && (!automatic || api.settings.get("auto_login"))) { return _converse.startNewPreboundBOSHSession(); } } @@ -100,7 +101,7 @@ export default { promise.resolve(); } - _converse.connection.setDisconnectionCause(_converse.LOGOUT, undefined, true); + _converse.connection.setDisconnectionCause(LOGOUT, undefined, true); if (_converse.connection !== undefined) { api.listen.once('disconnected', () => complete()); _converse.connection.disconnect(); diff --git a/src/headless/shared/connection/index.js b/src/headless/shared/connection/index.js index 7ba59b9d8b..2528865140 100644 --- a/src/headless/shared/connection/index.js +++ b/src/headless/shared/connection/index.js @@ -1,7 +1,7 @@ import debounce from 'lodash-es/debounce'; import log from "../../log.js"; import sizzle from 'sizzle'; -import { BOSH_WAIT } from '../../shared/constants.js'; +import { ANONYMOUS, BOSH_WAIT, LOGOUT } from '../../shared/constants.js'; import { CONNECTION_STATUS } from '../constants'; import { Strophe } from 'strophe.js/src/core.js'; import { _converse, api } from "../../core.js"; @@ -128,7 +128,7 @@ export class Connection extends Strophe.Connection { this._proto = new Strophe.Bosh(this); this.service = api.settings.get('bosh_service_url'); } else if (api.connection.isType('bosh') && api.settings.get("websocket_url")) { - if (api.settings.get("authentication") === _converse.ANONYMOUS) { + if (api.settings.get("authentication") === ANONYMOUS) { // When reconnecting anonymously, we need to connect with only // the domain, not the full JID that we had in our previous // (now failed) session. @@ -150,7 +150,7 @@ export class Connection extends Strophe.Connection { const conn_status = _converse.connfeedback.get('connection_status'); if (conn_status === Strophe.Status.CONNFAIL) { this.switchTransport(); - } else if (conn_status === Strophe.Status.AUTHFAIL && api.settings.get("authentication") === _converse.ANONYMOUS) { + } else if (conn_status === Strophe.Status.AUTHFAIL && api.settings.get("authentication") === ANONYMOUS) { // When reconnecting anonymously, we need to connect with only // the domain, not the full JID that we had in our previous // (now failed) session. @@ -164,7 +164,7 @@ export class Connection extends Strophe.Connection { */ api.trigger('will-reconnect'); - if (api.settings.get("authentication") === _converse.ANONYMOUS) { + if (api.settings.get("authentication") === ANONYMOUS) { await clearSession(); } return api.user.login(); @@ -265,7 +265,7 @@ export class Connection extends Strophe.Connection { if (api.settings.get("auto_reconnect")) { const reason = this.disconnection_reason; if (this.disconnection_cause === Strophe.Status.AUTHFAIL) { - if (api.settings.get("credentials_url") || api.settings.get("authentication") === _converse.ANONYMOUS) { + if (api.settings.get("credentials_url") || api.settings.get("authentication") === ANONYMOUS) { // If `credentials_url` is set, we reconnect, because we might // be receiving expirable tokens from the credentials_url. // @@ -287,7 +287,7 @@ export class Connection extends Strophe.Connection { ); return this.finishDisconnection(); } else if ( - this.disconnection_cause === _converse.LOGOUT || + this.disconnection_cause === LOGOUT || reason === Strophe.ErrorCondition.NO_AUTH_MECH || reason === "host-unknown" || reason === "remote-connection-failed" diff --git a/src/headless/shared/constants.js b/src/headless/shared/constants.js index 9c0686f736..800e702910 100644 --- a/src/headless/shared/constants.js +++ b/src/headless/shared/constants.js @@ -13,6 +13,35 @@ export const STATUS_WEIGHTS = { online: 1, }; +export const ANONYMOUS = 'anonymous'; +export const CLOSED = 'closed'; +export const EXTERNAL = 'external'; +export const LOGIN = 'login'; +export const LOGOUT = 'logout'; +export const OPENED = 'opened'; +export const PREBIND = 'prebind'; +export const SUCCESS = 'success'; +export const FAILURE = 'failure'; + +// Generated from css/images/user.svg +export const DEFAULT_IMAGE_TYPE = 'image/svg+xml'; +export const DEFAULT_IMAGE = + 'PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCI+CiA8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgZmlsbD0iIzU1NSIvPgogPGNpcmNsZSBjeD0iNjQiIGN5PSI0MSIgcj0iMjQiIGZpbGw9IiNmZmYiLz4KIDxwYXRoIGQ9Im0yOC41IDExMiB2LTEyIGMwLTEyIDEwLTI0IDI0LTI0IGgyMyBjMTQgMCAyNCAxMiAyNCAyNCB2MTIiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg=='; + +// XEP-0085 Chat states +// https =//xmpp.org/extensions/xep-0085.html +export const INACTIVE = 'inactive'; +export const ACTIVE = 'active'; +export const COMPOSING = 'composing'; +export const PAUSED = 'paused'; +export const GONE = 'gone'; + +// Chat types +export const PRIVATE_CHAT_TYPE = 'chatbox'; +export const CHATROOMS_TYPE = 'chatroom'; +export const HEADLINES_TYPE = 'headline'; +export const CONTROLBOX_TYPE = 'controlbox'; + export const CONNECTION_STATUS = {}; CONNECTION_STATUS[Strophe.Status.ATTACHED] = 'ATTACHED'; CONNECTION_STATUS[Strophe.Status.AUTHENTICATING] = 'AUTHENTICATING'; diff --git a/src/headless/shared/errors.js b/src/headless/shared/errors.js index 2dabc6cdec..3174fa3104 100644 --- a/src/headless/shared/errors.js +++ b/src/headless/shared/errors.js @@ -1,5 +1,5 @@ /** * Custom error for indicating timeouts - * @namespace _converse + * @namespace converse.env */ export class TimeoutError extends Error {} diff --git a/src/headless/utils/init.js b/src/headless/utils/init.js index 0e0d99c7bc..700f00aeb2 100644 --- a/src/headless/utils/init.js +++ b/src/headless/utils/init.js @@ -4,7 +4,7 @@ import debounce from 'lodash-es/debounce'; import localDriver from 'localforage-webextensionstorage-driver/local'; import log from '../log.js'; import syncDriver from 'localforage-webextensionstorage-driver/sync'; -import { CORE_PLUGINS } from '../shared/constants.js'; +import { ANONYMOUS, CORE_PLUGINS, EXTERNAL, LOGIN, PREBIND } from '../shared/constants.js'; import { Connection, MockConnection } from '../shared/connection/index.js'; import { Model } from '@converse/skeletor/src/model.js'; import { Strophe } from 'strophe.js/src/strophe'; @@ -43,7 +43,7 @@ export function initConnection () { const api = _converse.api; if (! api.settings.get('bosh_service_url')) { - if (api.settings.get("authentication") === _converse.PREBIND) { + if (api.settings.get("authentication") === PREBIND) { throw new Error("authentication is set to 'prebind' but we don't have a BOSH connection"); } } @@ -182,7 +182,7 @@ function initPersistentStorage (_converse, store_name) { function saveJIDtoSession (_converse, jid) { jid = _converse.session.get('jid') || jid; - if (_converse.api.settings.get("authentication") !== _converse.ANONYMOUS && !Strophe.getResourceFromJid(jid)) { + if (_converse.api.settings.get("authentication") !== ANONYMOUS && !Strophe.getResourceFromJid(jid)) { jid = jid.toLowerCase() + Connection.generateResource(); } _converse.jid = jid; @@ -387,7 +387,7 @@ async function getLoginCredentialsFromSCRAMKeys () { export async function attemptNonPreboundSession (credentials, automatic) { const { api } = _converse; - if (api.settings.get("authentication") === _converse.LOGIN) { + if (api.settings.get("authentication") === LOGIN) { // XXX: If EITHER ``keepalive`` or ``auto_login`` is ``true`` and // ``authentication`` is set to ``login``, then Converse will try to log the user in, // since we don't have a way to distinguish between wether we're @@ -417,7 +417,7 @@ export async function attemptNonPreboundSession (credentials, automatic) { if (!_converse.isTestEnv()) log.warn("attemptNonPreboundSession: Couldn't find credentials to log in with"); } else if ( - [_converse.ANONYMOUS, _converse.EXTERNAL].includes(api.settings.get("authentication")) && + [ANONYMOUS, EXTERNAL].includes(api.settings.get("authentication")) && (!automatic || api.settings.get("auto_login")) ) { connect(); @@ -446,7 +446,7 @@ export async function savedLoginInfo (jid) { async function connect (credentials) { const { api } = _converse; - if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(api.settings.get("authentication"))) { + if ([ANONYMOUS, EXTERNAL].includes(api.settings.get("authentication"))) { if (!_converse.jid) { throw new Error("Config Error: when using anonymous login " + "you need to provide the server's domain via the 'jid' option. " + @@ -457,7 +457,7 @@ async function connect (credentials) { _converse.connection.reset(); } _converse.connection.connect(_converse.jid.toLowerCase()); - } else if (api.settings.get("authentication") === _converse.LOGIN) { + } else if (api.settings.get("authentication") === LOGIN) { const password = credentials?.password ?? (_converse.connection?.pass || api.settings.get("password")); if (!password) { if (api.settings.get("auto_login")) { diff --git a/src/plugins/controlbox/controlbox.js b/src/plugins/controlbox/controlbox.js index 684fd6bed4..6611ca7240 100644 --- a/src/plugins/controlbox/controlbox.js +++ b/src/plugins/controlbox/controlbox.js @@ -1,6 +1,7 @@ import tplControlbox from './templates/controlbox.js'; import { CustomElement } from 'shared/components/element.js'; import { _converse, api, converse } from '@converse/headless/core.js'; +import { LOGOUT } from '@converse/headless/shared/constants.js'; const u = converse.env.utils; @@ -49,7 +50,7 @@ class ControlBox extends CustomElement { ev?.preventDefault?.(); if ( ev?.name === 'closeAllChatBoxes' && - (_converse.disconnection_cause !== _converse.LOGOUT || + (_converse.disconnection_cause !== LOGOUT || api.settings.get('show_controlbox_by_default')) ) { return; diff --git a/src/plugins/controlbox/loginform.js b/src/plugins/controlbox/loginform.js index a350920f4b..0636aad6f0 100644 --- a/src/plugins/controlbox/loginform.js +++ b/src/plugins/controlbox/loginform.js @@ -1,5 +1,6 @@ import bootstrap from 'bootstrap.native'; import tplLoginPanel from './templates/loginform.js'; +import { ANONYMOUS } from '@converse/headless/shared/constants'; import { CustomElement } from 'shared/components/element.js'; import { _converse, api, converse } from '@converse/headless/core.js'; import { initConnection } from '@converse/headless/utils/init.js'; @@ -36,7 +37,7 @@ class LoginForm extends CustomElement { async onLoginFormSubmitted (ev) { ev?.preventDefault(); - if (api.settings.get('authentication') === _converse.ANONYMOUS) { + if (api.settings.get('authentication') === ANONYMOUS) { return this.connect(_converse.jid); } diff --git a/src/plugins/controlbox/templates/loginform.js b/src/plugins/controlbox/templates/loginform.js index 17eaaa85c3..e68015ca0a 100644 --- a/src/plugins/controlbox/templates/loginform.js +++ b/src/plugins/controlbox/templates/loginform.js @@ -1,32 +1,41 @@ import 'shared/components/brand-heading.js'; import tplSpinner from 'templates/spinner.js'; -import { CONNECTION_STATUS } from '@converse/headless/shared/constants'; +import { ANONYMOUS, EXTERNAL, LOGIN, PREBIND, CONNECTION_STATUS } from '@converse/headless/shared/constants'; import { REPORTABLE_STATUSES, PRETTY_CONNECTION_STATUS, CONNECTION_STATUS_CSS_CLASS } from '../constants.js'; import { __ } from 'i18n'; -import { _converse, api } from "@converse/headless/core"; -import { html } from "lit"; - +import { _converse, api } from '@converse/headless/core'; +import { html } from 'lit'; const trust_checkbox = (checked) => { const i18n_hint_trusted = __( - 'To improve performance, we cache your data in this browser. '+ - 'Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. '+ - 'It\'s important that you explicitly log out, otherwise not all cached data might be deleted. '+ - 'Please note, when using an untrusted device, OMEMO encryption is NOT available.') + 'To improve performance, we cache your data in this browser. ' + + 'Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. ' + + "It's important that you explicitly log out, otherwise not all cached data might be deleted. " + + 'Please note, when using an untrusted device, OMEMO encryption is NOT available.' + ); const i18n_trusted = __('This is a trusted device'); return html` `; -} +}; const connection_url_input = () => { const i18n_connection_url = __('Connection URL'); @@ -36,53 +45,60 @@ const connection_url_input = () => {

${i18n_form_help}

- +
`; -} +}; const password_input = () => { const i18n_password = __('Password'); return html`
- + placeholder="${i18n_password}" + />
`; -} +}; const tplRegisterLink = () => { - const i18n_create_account = __("Create an account"); + const i18n_create_account = __('Create an account'); const i18n_hint_no_account = __("Don't have a chat account?"); return html`

${i18n_hint_no_account}

-

+

+ +

`; -} +}; const tplShowRegisterLink = () => { - return api.settings.get('allow_registration') && - !api.settings.get("auto_login") && - _converse.pluggable.plugins['converse-register'].enabled(_converse); -} - + return ( + api.settings.get('allow_registration') && + !api.settings.get('auto_login') && + _converse.pluggable.plugins['converse-register'].enabled(_converse) + ); +}; const auth_fields = (el) => { const authentication = api.settings.get('authentication'); const i18n_login = __('Log in'); - const i18n_xmpp_address = __("XMPP Address"); + const i18n_xmpp_address = __('XMPP Address'); const locked_domain = api.settings.get('locked_domain'); const default_domain = api.settings.get('default_domain'); const placeholder_username = ((locked_domain || default_domain) && __('Username')) || __('user@domain'); @@ -91,7 +107,8 @@ const auth_fields = (el) => { return html`
- { class="form-control" type="text" name="jid" - placeholder="${placeholder_username}"/> + placeholder="${placeholder_username}" + />
- ${ (authentication !== _converse.EXTERNAL) ? password_input() : '' } - ${ api.settings.get('show_connection_url_input') ? connection_url_input() : '' } - ${ show_trust_checkbox ? trust_checkbox(show_trust_checkbox === 'off' ? false : true) : '' } + ${authentication !== EXTERNAL ? password_input() : ''} + ${api.settings.get('show_connection_url_input') ? connection_url_input() : ''} + ${show_trust_checkbox ? trust_checkbox(show_trust_checkbox === 'off' ? false : true) : ''}
- +
- ${ tplShowRegisterLink() ? tplRegisterLink(el) : '' } + ${tplShowRegisterLink() ? tplRegisterLink() : ''} `; -} - +}; const form_fields = (el) => { const authentication = api.settings.get('authentication'); - const { ANONYMOUS, EXTERNAL, LOGIN, PREBIND } = _converse; const i18n_disconnected = __('Disconnected'); const i18n_anon_login = __('Click here to log in anonymously'); return html` - ${ (authentication == LOGIN || authentication == EXTERNAL) ? auth_fields(el) : '' } - ${ authentication == ANONYMOUS ? html`` : '' } - ${ authentication == PREBIND ? html`

${i18n_disconnected}

` : '' } + ${authentication == LOGIN || authentication == EXTERNAL ? auth_fields(el) : ''} + ${authentication == ANONYMOUS + ? html`` + : ''} + ${authentication == PREBIND ? html`

${i18n_disconnected}

` : ''} `; -} - +}; export default (el) => { const connection_status = _converse.connfeedback.get('connection_status'); @@ -133,13 +150,14 @@ export default (el) => { feedback_class = CONNECTION_STATUS_CSS_CLASS[connection_status]; } const conn_feedback_message = _converse.connfeedback.get('message'); - return html` - + return html`
-
`; diff --git a/src/shared/chat/message.js b/src/shared/chat/message.js index 6c0afc99a6..666a105768 100644 --- a/src/shared/chat/message.js +++ b/src/shared/chat/message.js @@ -118,6 +118,11 @@ export default class Message extends CustomElement { } renderChatMessage () { + if ( _converse.pluggable.plugins['converse-blocking']?.enabled(_converse) && + api.blockedUsers()?.has(this.getProps()?.properties?.jid) + ) { + return; + } return tplMessage(this, this.getProps()); } diff --git a/src/shared/modals/templates/user-details.js b/src/shared/modals/templates/user-details.js index 55d9a03d5f..ff651e6503 100644 --- a/src/shared/modals/templates/user-details.js +++ b/src/shared/modals/templates/user-details.js @@ -4,6 +4,17 @@ import { api } from "@converse/headless/core"; import { html } from 'lit'; import { modal_close_button } from "plugins/modal/templates/buttons.js"; + +const block_button = (o) => { + const i18n_block = __("Unblock"); + return html``; +} + +const unblock_button = (o) => { + const i18n_unblock = __("Unblock"); + return html``; +} + const remove_button = (el) => { const i18n_remove_contact = __('Remove as contact'); return html` @@ -19,6 +30,10 @@ const remove_button = (el) => { } export const tplFooter = (el) => { + const vcard = el.model?.vcard; + const vcard_json = vcard ? vcard.toJSON() : {}; + const o = { ...el.model.toJSON(), ...vcard_json }; + const is_roster_contact = el.model.contact !== undefined; const i18n_refresh = __('Refresh'); const allow_contact_removal = api.settings.get('allow_contact_removal'); @@ -33,6 +48,7 @@ export const tplFooter = (el) => { > ${i18n_refresh} ${ (allow_contact_removal && is_roster_contact) ? remove_button(el) : '' } + ${ api.blockedUsers ? ((api.blockedUsers()?.has(o.jid)) ? unblock_button(o) : block_button(o)) : '' } `; } From cafaa3e58e4bfb22eb4ce8ff3686775f8a04465c Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 20 Feb 2023 22:59:16 +0100 Subject: [PATCH 7/8] Properly query disco whether blocking is supported --- src/headless/plugins/blocking/utils.js | 6 ++---- src/headless/plugins/muc/muc.js | 14 ++++++++++---- src/plugins/muc-views/chatarea.js | 6 ++++-- src/plugins/muc-views/heading.js | 4 ++-- .../modals/templates/occupant_modal_footer.js | 7 ++++--- src/plugins/muc-views/utils.js | 4 ++-- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/headless/plugins/blocking/utils.js b/src/headless/plugins/blocking/utils.js index 22fac63735..f8faedb98c 100644 --- a/src/headless/plugins/blocking/utils.js +++ b/src/headless/plugins/blocking/utils.js @@ -1,10 +1,8 @@ import { _converse, api, converse } from "@converse/headless/core.js"; -const { Strophe, $iq } = converse.env; +const { Strophe } = converse.env; -export async function onConnected () { +export function onConnected () { api.refreshBlocklist(); _converse.connection.addHandler(api.handleBlockingStanza, Strophe.NS.BLOCKING, 'iq', 'set', null, null); } - - diff --git a/src/headless/plugins/muc/muc.js b/src/headless/plugins/muc/muc.js index fea2e8a058..96cb8fef5f 100644 --- a/src/headless/plugins/muc/muc.js +++ b/src/headless/plugins/muc/muc.js @@ -1337,28 +1337,34 @@ const ChatRoomMixin = { return true; }, - getAllowedCommands () { + async getAllowedCommands () { let allowed_commands = ['clear', 'help', 'me', 'nick', 'register']; + // Only allow blocking commands when server supports it and we also support it - if ( _converse.disco_entities.get(_converse.domain, true)?.features?.findWhere({'var': Strophe.NS.BLOCKING}) && - ( _converse.pluggable.plugins['converse-blocking']?.enabled(_converse) ) - ) { + if ( + await api.disco.supports(Strophe.NS.BLOCKING, _converse.domain) && + _converse.pluggable.plugins['converse-blocking']?.enabled(_converse) + ) { allowed_commands = [...allowed_commands, ...['block', 'unblock']]; } + if (this.config.get('changesubject') || ['owner', 'admin'].includes(this.getOwnAffiliation())) { allowed_commands = [...allowed_commands, ...['subject', 'topic']]; } + const occupant = this.occupants.findWhere({ 'jid': _converse.bare_jid }); if (this.verifyAffiliations(['owner'], occupant, false)) { allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS); } else if (this.verifyAffiliations(['admin'], occupant, false)) { allowed_commands = allowed_commands.concat(ADMIN_COMMANDS); } + if (this.verifyRoles(['moderator'], occupant, false)) { allowed_commands = allowed_commands.concat(MODERATOR_COMMANDS).concat(VISITOR_COMMANDS); } else if (!this.verifyRoles(['visitor', 'participant', 'moderator'], occupant, false)) { allowed_commands = allowed_commands.concat(VISITOR_COMMANDS); } + allowed_commands.sort(); if (Array.isArray(api.settings.get('muc_disable_slash_commands'))) { diff --git a/src/plugins/muc-views/chatarea.js b/src/plugins/muc-views/chatarea.js index ef665d951b..ed226612eb 100644 --- a/src/plugins/muc-views/chatarea.js +++ b/src/plugins/muc-views/chatarea.js @@ -49,9 +49,11 @@ export default class MUCChatArea extends CustomElement { ); } - getHelpMessages () { + async getHelpMessages () { const setting = api.settings.get('muc_disable_slash_commands'); const disabled_commands = Array.isArray(setting) ? setting : []; + const allowed_commands = await this.model.getAllowedCommands(); + return [ `/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user by changing their affiliation to outcast')}`, @@ -75,7 +77,7 @@ export default class MUCChatArea extends CustomElement { `/voice: ${__('Allow muted user to post messages')}` ] .filter(line => disabled_commands.every(c => !line.startsWith(c + '<', 9))) - .filter(line => this.model.getAllowedCommands().some(c => line.startsWith(c + '<', 9))); + .filter(line => allowed_commands.some(c => line.startsWith(c + '<', 9))); } onMousedown (ev) { diff --git a/src/plugins/muc-views/heading.js b/src/plugins/muc-views/heading.js index c2c7db6185..ff803ebd5c 100644 --- a/src/plugins/muc-views/heading.js +++ b/src/plugins/muc-views/heading.js @@ -79,7 +79,7 @@ export default class MUCHeading extends CustomElement { * Returns a list of objects which represent buttons for the groupchat header. * @emits _converse#getHeadingButtons */ - getHeadingButtons (subject_hidden) { + async getHeadingButtons (subject_hidden) { const buttons = []; buttons.push({ 'i18n_text': __('Details'), @@ -137,7 +137,7 @@ export default class MUCHeading extends CustomElement { const conn_status = this.model.session.get('connection_status'); if (conn_status === converse.ROOMSTATUS.ENTERED) { - const allowed_commands = this.model.getAllowedCommands(); + const allowed_commands = await this.model.getAllowedCommands(); if (allowed_commands.includes('modtools')) { buttons.push({ 'i18n_text': __('Moderate'), diff --git a/src/plugins/muc-views/modals/templates/occupant_modal_footer.js b/src/plugins/muc-views/modals/templates/occupant_modal_footer.js index 6a7c780ed1..af29687069 100644 --- a/src/plugins/muc-views/modals/templates/occupant_modal_footer.js +++ b/src/plugins/muc-views/modals/templates/occupant_modal_footer.js @@ -1,6 +1,7 @@ import { __ } from 'i18n'; import { _converse, api } from "@converse/headless/core"; import { html } from "lit"; +import { until } from 'lit/directives/until.js'; import { showOccupantModal, setRole, verifyAndSetAffiliation } from "../../utils.js" @@ -32,7 +33,7 @@ export default (el) => { const handleOwner = async (ev) => { const confirmed = await _converse.api.confirm( __(`Are you sure you want to promote %1$s?`, jid), - [ __("Promoting a user to owner may be irreversible."+ + [ __("Promoting a user to owner may be irreversible. "+ "Only server administrators may demote an owner of a Multi User Chat.")], []).then((x) => x.length === 0); @@ -142,7 +143,7 @@ export default (el) => { } }; - const applicable_buttons = (muc?.getAllowedCommands() ?? []).map(determineApplicable).filter(x => x); + const buttons = muc?.getAllowedCommands()?.then(commands => commands.map(determineApplicable).filter(x => x)); - return applicable_buttons ? html`` : null; + return html`${until(buttons?.then((buttons) => buttons ? html`` : ''), '')}`; } diff --git a/src/plugins/muc-views/utils.js b/src/plugins/muc-views/utils.js index 4528c3d3ce..dbf80efd5e 100644 --- a/src/plugins/muc-views/utils.js +++ b/src/plugins/muc-views/utils.js @@ -239,7 +239,7 @@ export function showOccupantModal (ev, occupant) { } -export function parseMessageForMUCCommands (data, handled) { +export async function parseMessageForMUCCommands (data, handled) { const model = data.model; if (handled || model.get('type') !== _converse.CHATROOMS_TYPE || ( @@ -257,7 +257,7 @@ export function parseMessageForMUCCommands (data, handled) { } const args = text.slice(('/' + command).length + 1).trim(); - const allowed_commands = model.getAllowedCommands() ?? []; + const allowed_commands = await model.getAllowedCommands() ?? []; if (command === 'admin' && allowed_commands.includes(command)) { verifyAndSetAffiliation(model, command, args, ['owner']); From 81e886815436303b3b7219a6aad35f7b0e2fc933 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 2 Mar 2023 14:50:11 +0100 Subject: [PATCH 8/8] Ran prettier on new files --- src/headless/plugins/blocking/api.js | 72 ++++--- src/headless/plugins/blocking/index.js | 21 +- .../modals/templates/occupant_modal_footer.js | 199 +++++++++++------- 3 files changed, 169 insertions(+), 123 deletions(-) diff --git a/src/headless/plugins/blocking/api.js b/src/headless/plugins/blocking/api.js index db39dc7ff9..7141629949 100644 --- a/src/headless/plugins/blocking/api.js +++ b/src/headless/plugins/blocking/api.js @@ -1,5 +1,5 @@ import log from '@converse/headless/log.js'; -import { _converse, api, converse } from "@converse/headless/core.js"; +import { _converse, api, converse } from '@converse/headless/core.js'; const { Strophe, $iq, sizzle, u } = converse.env; @@ -12,7 +12,7 @@ export default { */ async refreshBlocklist () { const features = await api.disco.getFeatures(_converse.domain); - if (!features?.findWhere({'var': Strophe.NS.BLOCKING})) { + if (!features?.findWhere({ 'var': Strophe.NS.BLOCKING })) { return false; } if (!_converse.connection) { @@ -20,11 +20,14 @@ export default { } 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 }); + '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; @@ -37,8 +40,8 @@ export default { return false; } - const blocklist = sizzle('item', result).map(item => item.getAttribute('jid')); - _converse.blocked.set({'set': new Set(blocklist)}); + const blocklist = sizzle('item', result).map((item) => item.getAttribute('jid')); + _converse.blocked.set({ 'set': new Set(blocklist) }); return true; }, @@ -48,15 +51,15 @@ export default { * @method api.handleBlockingStanza * @param { Object } [stanza] - The incoming stanza to handle */ - handleBlockingStanza ( stanza ) { + handleBlockingStanza (stanza) { if (stanza.firstElementChild.tagName === 'block') { - const users_to_block = sizzle('item', stanza).map(item => item.getAttribute('jid')); + 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')); + 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."); + 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 @@ -69,25 +72,28 @@ export default { * * @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})) { + 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 }); + 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); + 'type': 'set', + 'id': u.getUniqueId('block'), + }).cnode(block_element); - const result = await api.sendIQ(iq).catch(e => { log.fatal(e); return false }); + 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); @@ -106,25 +112,28 @@ export default { * @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})) { + 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}); + 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); + 'type': 'set', + 'id': u.getUniqueId('block'), + }).cnode(unblock_element); - const result = await api.sendIQ(iq).catch(e => { log.fatal(e); return false }); + 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); @@ -144,6 +153,5 @@ export default { */ blockedUsers () { return _converse.blocked.get('set'); - } - -} + }, +}; diff --git a/src/headless/plugins/blocking/index.js b/src/headless/plugins/blocking/index.js index 74bdb4c324..e8f13fae2c 100644 --- a/src/headless/plugins/blocking/index.js +++ b/src/headless/plugins/blocking/index.js @@ -1,10 +1,10 @@ /** * @description * Converse.js plugin which adds support for XEP-0191: Blocking - * Allows users to block other users, which hides their messages + * 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 { _converse, api, converse } from '@converse/headless/core.js'; import { onConnected } from './utils.js'; import { Model } from '@converse/skeletor/src/model.js'; @@ -14,20 +14,17 @@ const SetModel = Model.extend({ defaults: { 'set': new Set(), 'len': 0, - } + }, }); -Strophe.addNamespace('BLOCKING', "urn:xmpp:blocking"); +Strophe.addNamespace('BLOCKING', 'urn:xmpp:blocking'); converse.plugins.add('converse-blocking', { - enabled (_converse) { - return ( - !_converse.api.settings.get('blacklisted_plugins').includes('converse-blocking') - ); - }, - + dependencies: ['converse-disco'], - dependencies: ["converse-disco"], + enabled () { + return !api.settings.get('blacklisted_plugins').includes('converse-blocking'); + }, initialize () { _converse.blocked = new SetModel(); @@ -35,5 +32,5 @@ converse.plugins.add('converse-blocking', { api.listen.on('discoInitialized', onConnected); api.listen.on('reconnected', onConnected); - } + }, }); diff --git a/src/plugins/muc-views/modals/templates/occupant_modal_footer.js b/src/plugins/muc-views/modals/templates/occupant_modal_footer.js index af29687069..32724b62d5 100644 --- a/src/plugins/muc-views/modals/templates/occupant_modal_footer.js +++ b/src/plugins/muc-views/modals/templates/occupant_modal_footer.js @@ -1,9 +1,8 @@ import { __ } from 'i18n'; -import { _converse, api } from "@converse/headless/core"; -import { html } from "lit"; +import { _converse, api } from '@converse/headless/core'; +import { html } from 'lit'; import { until } from 'lit/directives/until.js'; -import { showOccupantModal, setRole, verifyAndSetAffiliation } from "../../utils.js" - +import { showOccupantModal, setRole, verifyAndSetAffiliation } from '../../utils.js'; export default (el) => { const model = el.model ?? el.message; @@ -26,40 +25,48 @@ export default (el) => { const handleVoice = () => setRole(muc, 'voice', jid, [], ['moderator']); const handleOp = () => setRole(muc, 'op', jid, ['admin', 'owner'], ['moderator']); const handleDeOp = () => setRole(muc, 'deop', jid, ['admin', 'owner'], ['moderator']); - const handleBan = () => verifyAndSetAffiliation(muc, 'ban', jid, ["admin", "owner"]); - const handleMember = () => verifyAndSetAffiliation(muc, 'member', jid, ["admin", "owner"]); - const handleAdmin = () => verifyAndSetAffiliation(muc, 'admin', jid, ["admin", "owner"]); + const handleBan = () => verifyAndSetAffiliation(muc, 'ban', jid, ['admin', 'owner']); + const handleMember = () => verifyAndSetAffiliation(muc, 'member', jid, ['admin', 'owner']); + const handleAdmin = () => verifyAndSetAffiliation(muc, 'admin', jid, ['admin', 'owner']); const handleOwner = async (ev) => { - const confirmed = await _converse.api.confirm( - __(`Are you sure you want to promote %1$s?`, jid), - [ __("Promoting a user to owner may be irreversible. "+ - "Only server administrators may demote an owner of a Multi User Chat.")], - []).then((x) => x.length === 0); + const confirmed = await _converse.api + .confirm( + __(`Are you sure you want to promote %1$s?`, jid), + [ + __( + 'Promoting a user to owner may be irreversible. ' + + 'Only server administrators may demote an owner of a Multi User Chat.' + ), + ], + [] + ) + .then((x) => x.length === 0); if (confirmed) { - verifyAndSetAffiliation(muc, 'owner', jid, ["admin", "owner"]); + verifyAndSetAffiliation(muc, 'owner', jid, ['admin', 'owner']); } else { showOccupantModal(ev, model); } }; - const blockButton = html`` - const unblockButton = html`` - const banButton = html`` - const kickButton = html`` + const blockButton = html``; + const unblockButton = html``; + const banButton = html``; + const kickButton = html``; - const muteButton = html`` - const unmuteButton = html`` - const memberButton = (memberText) => html`` - const addToChatButton = memberButton("Add to Chat"); - const unbanButton = memberButton("Unban"); - const removeAdminButton = memberButton("Remove Admin Status"); + const muteButton = html``; + const unmuteButton = html``; + const memberButton = (memberText) => + html``; + const addToChatButton = memberButton('Add to Chat'); + const unbanButton = memberButton('Unban'); + const removeAdminButton = memberButton('Remove Admin Status'); - const opButton = html`` - const deOpButton = html`` - const adminButton = html`` - const ownerButton = html`` + const opButton = html``; + const deOpButton = html``; + const adminButton = html``; + const ownerButton = html``; // The following table stores a map from Affiliation x Role -> Button // Mapping to a button rather than a boolean provides us with a bit more @@ -71,79 +78,113 @@ export default (el) => { // The table is more or less copied verbatim from ejabberd's role permissions table // Who can ban (set affiliation to outcast)? - const canBan = ({ - 'owner': [ 'none', 'member', 'admin' ].includes(affiliation) ? banButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) ? banButton : null, - 'member': [ 'none', 'member' ].includes(affiliation) ? banButton : ([ 'visitor', 'none', 'participant' ].includes(role) ? banButton : null), - })[ownAffiliation]; + const canBan = { + 'owner': ['none', 'member', 'admin'].includes(affiliation) ? banButton : null, + 'admin': ['none', 'member'].includes(affiliation) ? banButton : null, + 'member': ['none', 'member'].includes(affiliation) + ? banButton + : ['visitor', 'none', 'participant'].includes(role) + ? banButton + : null, + }[ownAffiliation]; // Who can kick (set role to none)? - const canKick = ({ - 'owner': [ 'none', 'member', 'admin' ].includes(affiliation) && role !== 'none' ? kickButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) && role !== 'none' ? kickButton : null, - 'member': [ 'none', 'member' ].includes(affiliation) && ![' none', 'moderator'].includes(role) ? kickButton : null, - })[ownAffiliation]; + const canKick = { + 'owner': ['none', 'member', 'admin'].includes(affiliation) && role !== 'none' ? kickButton : null, + 'admin': ['none', 'member'].includes(affiliation) && role !== 'none' ? kickButton : null, + 'member': + ['none', 'member'].includes(affiliation) && ![' none', 'moderator'].includes(role) ? kickButton : null, + }[ownAffiliation]; // Who can mute (set role to visitor)? - const canMute = ({ - 'owner': [ 'none', 'member' ].includes(affiliation) && ![ 'visitor' ].includes(role) ? muteButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) && ![ 'visitor' ].includes(role) ? muteButton : null, - 'member': [ 'none', 'member' ].includes(affiliation) && ![ 'visitor', 'moderator'].includes(role) ? muteButton : null, - })[ownAffiliation]; + const canMute = { + 'owner': ['none', 'member'].includes(affiliation) && !['visitor'].includes(role) ? muteButton : null, + 'admin': ['none', 'member'].includes(affiliation) && !['visitor'].includes(role) ? muteButton : null, + 'member': + ['none', 'member'].includes(affiliation) && !['visitor', 'moderator'].includes(role) ? muteButton : null, + }[ownAffiliation]; // Who can unmute (set role to participant)? - const canUnmute = ({ - 'owner': [ 'none', 'member' ].includes(affiliation) && [ 'visitor' ].includes(role) ? unmuteButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) && [ 'visitor' ].includes(role) ? unmuteButton : null, - 'member': [ 'none', 'member' ].includes(affiliation) && [ 'visitor' ].includes(role) ? unmuteButton : null, - })[ownAffiliation]; + const canUnmute = { + 'owner': ['none', 'member'].includes(affiliation) && ['visitor'].includes(role) ? unmuteButton : null, + 'admin': ['none', 'member'].includes(affiliation) && ['visitor'].includes(role) ? unmuteButton : null, + 'member': ['none', 'member'].includes(affiliation) && ['visitor'].includes(role) ? unmuteButton : null, + }[ownAffiliation]; // Who can set affiliation to member? - const canMember = ({ - 'owner': ({ 'admin': removeAdminButton, 'none': addToChatButton, 'outcast': unbanButton })[affiliation], - 'admin': ({ 'none': addToChatButton, 'outcast': unbanButton })[affiliation], - 'member': ({ 'none': addToChatButton })[affiliation], - })[ownAffiliation]; + const canMember = { + 'owner': { 'admin': removeAdminButton, 'none': addToChatButton, 'outcast': unbanButton }[affiliation], + 'admin': { 'none': addToChatButton, 'outcast': unbanButton }[affiliation], + 'member': { 'none': addToChatButton }[affiliation], + }[ownAffiliation]; // Who can promote to moderator role? - const canOp = ({ - 'owner': [ 'none', 'member' ].includes(affiliation) && [ 'none', 'participant' ].includes(role) ? opButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) && [ 'none', 'participant' ].includes(role) ? opButton : null, - })[ownAffiliation]; + const canOp = { + 'owner': ['none', 'member'].includes(affiliation) && ['none', 'participant'].includes(role) ? opButton : null, + 'admin': ['none', 'member'].includes(affiliation) && ['none', 'participant'].includes(role) ? opButton : null, + }[ownAffiliation]; // Who can remove moderator role? - const canDeOp = ({ - 'owner': [ 'none', 'member' ].includes(affiliation) && role === 'moderator' ? deOpButton : null, - 'admin': [ 'none', 'member' ].includes(affiliation) && role === 'moderator' ? deOpButton : null, - })[ownAffiliation]; + const canDeOp = { + 'owner': ['none', 'member'].includes(affiliation) && role === 'moderator' ? deOpButton : null, + 'admin': ['none', 'member'].includes(affiliation) && role === 'moderator' ? deOpButton : null, + }[ownAffiliation]; // Who can change affiliation to admin? - const canAdmin = ({ 'owner': [ 'none', 'member' ].includes(affiliation) ? adminButton : null, })[ownAffiliation]; + const canAdmin = { 'owner': ['none', 'member'].includes(affiliation) ? adminButton : null }[ownAffiliation]; // Who can change affiliation to owner? - const canOwner = ({ 'owner': [ 'none', 'member', 'admin' ].includes(affiliation) ? ownerButton : null, })[ownAffiliation]; - + const canOwner = { 'owner': ['none', 'member', 'admin'].includes(affiliation) ? ownerButton : null }[ + ownAffiliation + ]; const blocking_plug = _converse.pluggable.plugins['converse-blocking']?.enabled(_converse); - const determineApplicable = function(command) { + const determineApplicable = function (command) { switch (command) { - case('kick'): { return canKick; } - case('ban'): { return canBan; } - case('voice'): { return canUnmute; } - case('mute'): { return canMute; } - case('op'): { return canOp; } - case('deop'): { return canDeOp; } - case('member'): { return canMember; } - case('admin'): { return canAdmin; } - case('owner'): { return canOwner; } - case('block'): { return ( blocking_plug && jid && !api.blockedUsers().has(jid) ? blockButton : null ); } - case('unblock'): { return ( blocking_plug && jid && api.blockedUsers().has(jid) ? unblockButton : null ); } - default: { return null; } + case 'kick': { + return canKick; + } + case 'ban': { + return canBan; + } + case 'voice': { + return canUnmute; + } + case 'mute': { + return canMute; + } + case 'op': { + return canOp; + } + case 'deop': { + return canDeOp; + } + case 'member': { + return canMember; + } + case 'admin': { + return canAdmin; + } + case 'owner': { + return canOwner; + } + case 'block': { + return blocking_plug && jid && !api.blockedUsers().has(jid) ? blockButton : null; + } + case 'unblock': { + return blocking_plug && jid && api.blockedUsers().has(jid) ? unblockButton : null; + } + default: { + return null; + } } }; - const buttons = muc?.getAllowedCommands()?.then(commands => commands.map(determineApplicable).filter(x => x)); + const buttons = muc?.getAllowedCommands()?.then((commands) => commands.map(determineApplicable).filter((x) => x)); - return html`${until(buttons?.then((buttons) => buttons ? html`` : ''), '')}`; -} + return html`${until( + buttons?.then((buttons) => (buttons ? html`` : '')), + '' + )}`; +};