diff --git a/CHANGES.md b/CHANGES.md
index c454c3c2b0..b5418fcd55 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -43,7 +43,8 @@
- Fix: trying to use emojis with an uppercase letter breaks the message field.
- Fix: renaming getEmojisByAtrribute to getEmojisByAttribute.
-### Changes
+### Changes and features
+- Add support for XEP-0191 Blocking Command
- Upgrade to Bootstrap 5
- Add an occupants filter to the MUC sidebar
- Change contacts filter to rename the anachronistic `Online` state to `Available`.
diff --git a/conversejs.doap b/conversejs.doap
index 337a472f0f..c9568db77a 100644
--- a/conversejs.doap
+++ b/conversejs.doap
@@ -125,6 +125,12 @@
+
+
+
+ 11.0.0
+
+
diff --git a/karma.conf.js b/karma.conf.js
index 1ae20e72e4..6bd6bc88b8 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -24,6 +24,7 @@ module.exports = function(config) {
},
{ pattern: "src/shared/tests/mock.js", type: 'module' },
+ { pattern: "src/headless/plugins/blocklist/tests/blocklist.js", type: 'module' },
{ pattern: "src/headless/plugins/bookmarks/tests/bookmarks.js", type: 'module' },
{ pattern: "src/headless/plugins/bookmarks/tests/deprecated.js", type: 'module' },
{ pattern: "src/headless/plugins/caps/tests/caps.js", type: 'module' },
diff --git a/package-lock.json b/package-lock.json
index b0bcaf86f5..c3fa29cd84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9868,8 +9868,8 @@
},
"node_modules/strophe.js": {
"version": "3.1.0",
- "resolved": "git+ssh://git@github.com/strophe/strophejs.git#9b643a43b5cd79f0d8a2e0015e9d2c0d0fec8578",
- "integrity": "sha512-/T9ptJEZPy98XaGaFoNfvrzcRQDafRDjSY/Kq3Gk3ilMEmD2oDGslyIdFFYxjAgU6ON6Z1e0Z9CnzgXggqHtFQ==",
+ "resolved": "git+ssh://git@github.com/strophe/strophejs.git#b4f3369ba07dee4f04750de6709ea8ebe8adf9a5",
+ "integrity": "sha512-M/T9Pio3eG7GUzVmQUSNg+XzjFwQ6qhzI+Z3uSwUIItxxpRIB8lQB2Afb0L7lbQiRYB7/9tS03GxksdqjfrS5g==",
"license": "MIT",
"optionalDependencies": {
"@types/jsdom": "^21.1.7",
@@ -11121,7 +11121,7 @@
"pluggable.js": "3.0.1",
"sizzle": "^2.3.5",
"sprintf-js": "^1.1.2",
- "strophe.js": "strophe/strophejs#9b643a43b5cd79f0d8a2e0015e9d2c0d0fec8578",
+ "strophe.js": "strophe/strophejs#b4f3369ba07dee4f04750de6709ea8ebe8adf9a5",
"urijs": "^1.19.10"
},
"devDependencies": {}
diff --git a/src/headless/index.js b/src/headless/index.js
index 79fa78c1b2..f056e26fa1 100644
--- a/src/headless/index.js
+++ b/src/headless/index.js
@@ -15,6 +15,7 @@ import log from './log.js';
export { EmojiPicker } from './plugins/emoji/index.js';
export { Bookmark, Bookmarks } from './plugins/bookmarks/index.js'; // XEP-0199 XMPP Ping
+import './plugins/blocklist/index.js';
import './plugins/bosh/index.js'; // XEP-0206 BOSH
import './plugins/caps/index.js'; // XEP-0115 Entity Capabilities
export { ChatBox, Message, Messages } from './plugins/chat/index.js'; // RFC-6121 Instant messaging
diff --git a/src/headless/package.json b/src/headless/package.json
index 3c61030d40..1a5f7de063 100644
--- a/src/headless/package.json
+++ b/src/headless/package.json
@@ -42,7 +42,7 @@
"pluggable.js": "3.0.1",
"sizzle": "^2.3.5",
"sprintf-js": "^1.1.2",
- "strophe.js": "strophe/strophejs#9b643a43b5cd79f0d8a2e0015e9d2c0d0fec8578",
+ "strophe.js": "strophe/strophejs#b4f3369ba07dee4f04750de6709ea8ebe8adf9a5",
"urijs": "^1.19.10"
},
"devDependencies": {}
diff --git a/src/headless/plugins/blocklist/api.js b/src/headless/plugins/blocklist/api.js
new file mode 100644
index 0000000000..a7c7270d2f
--- /dev/null
+++ b/src/headless/plugins/blocklist/api.js
@@ -0,0 +1,51 @@
+import promise_api from '../../shared/api/promise.js';
+import { sendBlockStanza, sendUnblockStanza } from './utils.js';
+
+const { waitUntil } = promise_api;
+
+/**
+ * Groups methods relevant to XEP-0191 Blocking Command
+ * @namespace api.blocklist
+ * @memberOf api
+ */
+const blocklist = {
+ /**
+ * Retrieves the current user's blocklist
+ * @returns {Promise}
+ */
+ async get() {
+ return await waitUntil('blocklistInitialized');
+ },
+
+ /**
+ * Adds a new entity to the blocklist
+ * @param {string|string[]} jid
+ * @param {boolean} [send_stanza=true]
+ * @returns {Promise}
+ */
+ async add(jid, send_stanza = true) {
+ const blocklist = await waitUntil('blocklistInitialized');
+ const jids = Array.isArray(jid) ? jid : [jid];
+ if (send_stanza) await sendBlockStanza(jids);
+ jids.forEach((jid) => blocklist.create({ jid }));
+ return blocklist;
+ },
+
+ /**
+ * Removes an entity from the blocklist
+ * @param {string|string[]} jid
+ * @param {boolean} [send_stanza=true]
+ * @returns {Promise}
+ */
+ async remove(jid, send_stanza = true) {
+ const blocklist = await waitUntil('blocklistInitialized');
+ const jids = Array.isArray(jid) ? jid : [jid];
+ if (send_stanza) await sendUnblockStanza(jids);
+ blocklist.remove(jids);
+ return blocklist;
+ },
+};
+
+const blocklist_api = { blocklist };
+
+export default blocklist_api;
diff --git a/src/headless/plugins/blocklist/collection.js b/src/headless/plugins/blocklist/collection.js
new file mode 100644
index 0000000000..71ff417862
--- /dev/null
+++ b/src/headless/plugins/blocklist/collection.js
@@ -0,0 +1,88 @@
+import { getOpenPromise } from '@converse/openpromise';
+import { Collection } from '@converse/skeletor';
+import log from '../../log.js';
+import _converse from '../../shared/_converse.js';
+import { initStorage } from '../../utils/storage.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
+import BlockedEntity from './model.js';
+
+const { stx, u } = converse.env;
+
+class Blocklist extends Collection {
+ get idAttribute() {
+ return 'jid';
+ }
+
+ constructor() {
+ super();
+ this.model = BlockedEntity;
+ }
+
+ async initialize() {
+ const { session } = _converse;
+ const cache_key = `converse.blocklist-${session.get('bare_jid')}`;
+ this.fetched_flag = `${cache_key}-fetched`;
+ initStorage(this, cache_key);
+
+ await this.fetchBlocklist();
+
+ /**
+ * Triggered once the {@link Blocklist} collection
+ * has been created and cached blocklist have been fetched.
+ * @event _converse#blocklistInitialized
+ * @type {Blocklist}
+ * @example _converse.api.listen.on('blocklistInitialized', (blocklist) => { ... });
+ */
+ api.trigger('blocklistInitialized', this);
+ }
+
+ fetchBlocklist() {
+ const deferred = getOpenPromise();
+ if (window.sessionStorage.getItem(this.fetched_flag)) {
+ this.fetch({
+ success: () => deferred.resolve(),
+ error: () => deferred.resolve(),
+ });
+ } else {
+ this.fetchBlocklistFromServer(deferred);
+ }
+ return deferred;
+ }
+
+ /**
+ * @param {Object} deferred
+ */
+ async fetchBlocklistFromServer(deferred) {
+ const stanza = stx``;
+
+ try {
+ this.onBlocklistReceived(deferred, await api.sendIQ(stanza));
+ } catch (e) {
+ log.error(e);
+ deferred.resolve();
+ return;
+ }
+ }
+
+ /**
+ * @param {Object} deferred
+ * @param {Element} iq
+ */
+ async onBlocklistReceived(deferred, iq) {
+ Array.from(iq.querySelectorAll('blocklist item')).forEach((item) => {
+ const jid = item.getAttribute('jid');
+ const blocked = this.get(jid);
+ blocked ? blocked.save({ jid }) : this.create({ jid });
+ });
+
+ window.sessionStorage.setItem(this.fetched_flag, 'true');
+ if (deferred !== undefined) {
+ return deferred.resolve();
+ }
+ }
+}
+
+export default Blocklist;
diff --git a/src/headless/plugins/blocklist/index.js b/src/headless/plugins/blocklist/index.js
new file mode 100644
index 0000000000..1f1ffb1d1d
--- /dev/null
+++ b/src/headless/plugins/blocklist/index.js
@@ -0,0 +1 @@
+import './plugin.js';
diff --git a/src/headless/plugins/blocklist/model.js b/src/headless/plugins/blocklist/model.js
new file mode 100644
index 0000000000..aaae155bc6
--- /dev/null
+++ b/src/headless/plugins/blocklist/model.js
@@ -0,0 +1,16 @@
+import { Model } from '@converse/skeletor';
+import converse from '../../shared/api/public.js';
+
+const { Strophe } = converse.env;
+
+class BlockedEntity extends Model {
+ get idAttribute () {
+ return 'jid';
+ }
+
+ getDisplayName () {
+ return Strophe.xmlunescape(this.get('name'));
+ }
+}
+
+export default BlockedEntity;
diff --git a/src/headless/plugins/blocklist/plugin.js b/src/headless/plugins/blocklist/plugin.js
new file mode 100644
index 0000000000..8a552c69d3
--- /dev/null
+++ b/src/headless/plugins/blocklist/plugin.js
@@ -0,0 +1,73 @@
+/**
+ * @copyright The Converse.js contributors
+ * @license Mozilla Public License (MPLv2)
+ * @description Adds support for XEP-0191 Blocking Command
+ */
+import _converse from '../../shared/_converse.js';
+import api from '../../shared/api/index.js';
+import converse from '../../shared/api/public.js';
+import log from '../../log.js';
+import Blocklist from './collection.js';
+import BlockedEntity from './model.js';
+import blocklist_api from './api.js';
+
+const { Strophe, sizzle } = converse.env;
+
+Strophe.addNamespace('BLOCKING', 'urn:xmpp:blocking');
+
+converse.plugins.add('converse-blocking', {
+ dependencies: ['converse-disco'],
+
+ initialize() {
+ const exports = { Blocklist, BlockedEntity };
+ Object.assign(_converse.exports, exports);
+ Object.assign(api, blocklist_api);
+
+ api.promises.add(['blocklistInitialized']);
+
+ api.listen.on('connected', () => {
+ const connection = api.connection.get();
+ connection.addHandler(
+ /** @param {Element} stanza */ (stanza) => {
+ const bare_jid = _converse.session.get('bare_jid');
+ const from = stanza.getAttribute('from');
+ if (Strophe.getBareJidFromJid(from ?? bare_jid) != bare_jid) {
+ log.warn(`Received a blocklist push stanza from a suspicious JID ${from}`);
+ return true;
+ }
+
+ const add_jids = sizzle(`block[xmlns="${Strophe.NS.BLOCKING}"] item`, stanza).map(
+ /** @param {Element} item */ (item) => item.getAttribute('jid')
+ );
+ if (add_jids.length) api.blocklist.add(add_jids, false);
+
+ const remove_jids = sizzle(`unblock[xmlns="${Strophe.NS.BLOCKING}"] item`, stanza).map(
+ /** @param {Element} item */ (item) => item.getAttribute('jid')
+ );
+ if (remove_jids.length) api.blocklist.remove(remove_jids, false);
+
+ return true;
+ },
+ Strophe.NS.BLOCKING,
+ 'iq',
+ 'set'
+ );
+ });
+
+ api.listen.on('clearSession', () => {
+ const { state } = _converse;
+ if (state.blocklist) {
+ state.blocklist.clearStore({ 'silent': true });
+ window.sessionStorage.removeItem(state.blocklist.fetched_flag);
+ delete state.blocklist;
+ }
+ });
+
+ api.listen.on('discoInitialized', async () => {
+ const domain = _converse.session.get('domain');
+ if (await api.disco.supports(Strophe.NS.BLOCKING, domain)) {
+ _converse.state.blocklist = new _converse.exports.Blocklist();
+ }
+ });
+ },
+});
diff --git a/src/headless/plugins/blocklist/tests/blocklist.js b/src/headless/plugins/blocklist/tests/blocklist.js
new file mode 100644
index 0000000000..a54471b76a
--- /dev/null
+++ b/src/headless/plugins/blocklist/tests/blocklist.js
@@ -0,0 +1,176 @@
+/*global mock, converse */
+const { u, stx } = converse.env;
+
+fdescribe('A blocklist', function () {
+ beforeEach(() => {
+ jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza });
+ window.sessionStorage.removeItem('converse.blocklist-romeo@montague.lit-fetched');
+ });
+
+ it(
+ 'is automatically fetched from the server once the user logs in',
+ mock.initConverse(['discoInitialized'], {}, async function (_converse) {
+ const { api } = _converse;
+ await mock.waitUntilDiscoConfirmed(
+ _converse,
+ _converse.domain,
+ [{ 'category': 'server', 'type': 'IM' }],
+ ['urn:xmpp:blocking']
+ );
+ await mock.waitForRoster(_converse, 'current', 0);
+
+ const IQ_stanzas = _converse.api.connection.get().IQ_stanzas;
+ const sent_stanza = await u.waitUntil(() => IQ_stanzas.find((s) => s.querySelector('iq blocklist')));
+
+ expect(sent_stanza).toEqualStanza(stx`
+
+
+ `);
+
+ const stanza = stx`
+
+
+
+
+
+ `;
+ _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
+
+ const blocklist = await api.waitUntil('blocklistInitialized');
+ expect(blocklist.length).toBe(2);
+ expect(blocklist.models.map((m) => m.get('jid'))).toEqual(['iago@shakespeare.lit', 'juliet@capulet.lit']);
+ })
+ );
+
+ it(
+ 'is updated when the server sends IQ stanzas',
+ mock.initConverse(['discoInitialized'], {}, async function (_converse) {
+ const { api, domain } = _converse;
+ await mock.waitUntilDiscoConfirmed(
+ _converse,
+ domain,
+ [{ 'category': 'server', 'type': 'IM' }],
+ ['urn:xmpp:blocking']
+ );
+ await mock.waitForRoster(_converse, 'current', 0);
+
+ const IQ_stanzas = api.connection.get().IQ_stanzas;
+ let sent_stanza = await u.waitUntil(() => IQ_stanzas.find((s) => s.querySelector('iq blocklist')));
+
+ const stanza = stx`
+
+
+
+
+ `;
+ _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
+
+ const blocklist = await api.waitUntil('blocklistInitialized');
+ expect(blocklist.length).toBe(1);
+
+ // The server sends a push IQ stanza
+ _converse.api.connection.get()._dataRecv(
+ mock.createRequest(
+ stx`
+
+
+
+
+ `
+ )
+ );
+ await u.waitUntil(() => blocklist.length === 2);
+ expect(blocklist.models.map((m) => m.get('jid'))).toEqual(['iago@shakespeare.lit', 'juliet@capulet.lit']);
+
+ // The server sends a push IQ stanza
+ _converse.api.connection.get()._dataRecv(
+ mock.createRequest(
+ stx`
+
+
+
+
+ `
+ )
+ );
+ await u.waitUntil(() => blocklist.length === 1);
+ expect(blocklist.models.map((m) => m.get('jid'))).toEqual(['iago@shakespeare.lit']);
+ })
+ );
+
+ it(
+ 'can be updated via the api',
+ mock.initConverse(['discoInitialized'], {}, async function (_converse) {
+ const { api, domain } = _converse;
+ await mock.waitUntilDiscoConfirmed(
+ _converse,
+ domain,
+ [{ 'category': 'server', 'type': 'IM' }],
+ ['urn:xmpp:blocking']
+ );
+ await mock.waitForRoster(_converse, 'current', 0);
+
+ const IQ_stanzas = api.connection.get().IQ_stanzas;
+ let sent_stanza = await u.waitUntil(() => IQ_stanzas.find((s) => s.querySelector('iq blocklist')));
+
+ _converse.api.connection.get()._dataRecv(mock.createRequest(
+ stx`
+
+
+
+ `));
+
+ const blocklist = await api.waitUntil('blocklistInitialized');
+ expect(blocklist.length).toBe(1);
+
+ api.blocklist.add('juliet@capulet.lit');
+
+ sent_stanza = await u.waitUntil(() => IQ_stanzas.find((s) => s.querySelector('iq block')));
+ expect(sent_stanza).toEqualStanza(stx`
+
+
+
+
+ `);
+
+ _converse.api.connection.get()._dataRecv(mock.createRequest(
+ stx``)
+ );
+
+ await u.waitUntil(() => blocklist.length === 2);
+ expect(blocklist.models.map((m) => m.get('jid'))).toEqual(['iago@shakespeare.lit', 'juliet@capulet.lit']);
+
+ api.blocklist.remove('juliet@capulet.lit');
+
+ sent_stanza = await u.waitUntil(() => IQ_stanzas.find((s) => s.querySelector('iq unblock')));
+ expect(sent_stanza).toEqualStanza(stx`
+
+
+
+
+ `);
+
+ _converse.api.connection.get()._dataRecv(mock.createRequest(
+ stx``)
+ );
+
+ await u.waitUntil(() => blocklist.length === 1);
+ expect(blocklist.models.map((m) => m.get('jid'))).toEqual(['iago@shakespeare.lit']);
+ })
+ );
+});
diff --git a/src/headless/plugins/blocklist/utils.js b/src/headless/plugins/blocklist/utils.js
new file mode 100644
index 0000000000..b0732fba0f
--- /dev/null
+++ b/src/headless/plugins/blocklist/utils.js
@@ -0,0 +1,34 @@
+import converse from '../../shared/api/public.js';
+import send_api from '../../shared/api/send.js';
+
+const { Strophe, stx, u } = converse.env;
+
+/**
+ * Sends an IQ stanza to remove one or more JIDs from the blocklist
+ * @param {string|string[]} jid
+ */
+export async function sendUnblockStanza(jid) {
+ const jids = Array.isArray(jid) ? jid : [jid];
+ const stanza = stx`
+
+
+ ${jids.map((id) => stx` `)}
+
+ `;
+ await send_api.sendIQ(stanza);
+}
+
+/**
+ * Sends an IQ stanza to add one or more JIDs from the blocklist
+ * @param {string|string[]} jid
+ */
+export async function sendBlockStanza(jid) {
+ const jids = Array.isArray(jid) ? jid : [jid];
+ const stanza = stx`
+
+
+ ${jids.map((id) => stx` `)}
+
+ `;
+ await send_api.sendIQ(stanza);
+}
diff --git a/src/headless/plugins/bookmarks/api.js b/src/headless/plugins/bookmarks/api.js
index 2a2b773167..2343e9be1a 100644
--- a/src/headless/plugins/bookmarks/api.js
+++ b/src/headless/plugins/bookmarks/api.js
@@ -4,8 +4,7 @@ import promise_api from '../../shared/api/promise.js';
const { waitUntil } = promise_api;
/**
- * Groups methods relevant to XEP-0402 MUC bookmarks.
- *
+ * Groups methods relevant to XEP-0402 (and XEP-0048) MUC bookmarks.
* @namespace api.bookmarks
* @memberOf api
*/
diff --git a/src/headless/plugins/bookmarks/collection.js b/src/headless/plugins/bookmarks/collection.js
index 6537e5371e..0e6260da24 100644
--- a/src/headless/plugins/bookmarks/collection.js
+++ b/src/headless/plugins/bookmarks/collection.js
@@ -38,7 +38,7 @@ class Bookmarks extends Collection {
await this.fetchBookmarks();
/**
- * Triggered once the _converse.Bookmarks collection
+ * Triggered once the {@link Bookmarks} collection
* has been created and cached bookmarks have been fetched.
* @event _converse#bookmarksInitialized
* @type {Bookmarks}
diff --git a/src/headless/plugins/bookmarks/model.js b/src/headless/plugins/bookmarks/model.js
index 50036ce0cc..76a9ab3227 100644
--- a/src/headless/plugins/bookmarks/model.js
+++ b/src/headless/plugins/bookmarks/model.js
@@ -1,10 +1,10 @@
-import converse from '../../shared/api/public.js';
import { Model } from '@converse/skeletor';
+import converse from '../../shared/api/public.js';
const { Strophe } = converse.env;
class Bookmark extends Model {
- get idAttribute () { // eslint-disable-line class-methods-use-this
+ get idAttribute () {
return 'jid';
}
diff --git a/src/headless/plugins/disco/api.js b/src/headless/plugins/disco/api.js
index 797972319f..5254e30191 100644
--- a/src/headless/plugins/disco/api.js
+++ b/src/headless/plugins/disco/api.js
@@ -382,14 +382,6 @@ export default {
return entity.waitUntilFeaturesDiscovered;
},
- /**
- * @deprecated Use {@link api.disco.refresh} instead.
- * @method api.disco.refreshFeatures
- */
- refreshFeatures (jid) {
- return api.refresh(jid);
- },
-
/**
* Return all the features associated with a disco entity
*
diff --git a/src/headless/plugins/disco/entity.js b/src/headless/plugins/disco/entity.js
index 22f80eff88..a4ed6cd43d 100644
--- a/src/headless/plugins/disco/entity.js
+++ b/src/headless/plugins/disco/entity.js
@@ -70,7 +70,7 @@ class DiscoEntity extends Model {
*/
async getFeature (feature) {
await this.waitUntilFeaturesDiscovered;
- if (this.features.findWhere({ 'var': feature })) {
+ if (this.features.findWhere({ var: feature })) {
return this;
}
}
diff --git a/src/headless/plugins/disco/tests/disco.js b/src/headless/plugins/disco/tests/disco.js
index 29c71b7b1a..6e4a39ead7 100644
--- a/src/headless/plugins/disco/tests/disco.js
+++ b/src/headless/plugins/disco/tests/disco.js
@@ -1,5 +1,7 @@
/*global mock, converse */
+const { u, $iq, stx } = converse.env;
+
describe("Service Discovery", function () {
describe("Whenever a server is queried for its features", function () {
@@ -9,7 +11,6 @@ describe("Service Discovery", function () {
['discoInitialized'], {},
async function (_converse) {
- const { u, $iq } = converse.env;
const IQ_stanzas = _converse.api.connection.get().IQ_stanzas;
const IQ_ids = _converse.api.connection.get().IQ_ids;
await u.waitUntil(function () {
@@ -17,63 +18,27 @@ describe("Service Discovery", function () {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
}).length > 0;
});
- /*
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
let stanza = IQ_stanzas.find(function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
});
const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
- stanza = $iq({
- 'type': 'result',
- 'from': 'montague.lit',
- 'to': 'romeo@montague.lit/orchard',
- 'id': info_IQ_id
- }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
- .c('identity', {
- 'category': 'server',
- 'type': 'im'}).up()
- .c('identity', {
- 'category': 'conference',
- 'type': 'text',
- 'name': 'Play-Specific Chatrooms'}).up()
- .c('identity', {
- 'category': 'directory',
- 'type': 'chatroom',
- 'name': 'Play-Specific Chatrooms'}).up()
- .c('feature', {
- 'var': 'http://jabber.org/protocol/disco#info'}).up()
- .c('feature', {
- 'var': 'http://jabber.org/protocol/disco#items'}).up()
- .c('feature', {
- 'var': 'jabber:iq:register'}).up()
- .c('feature', {
- 'var': 'jabber:iq:time'}).up()
- .c('feature', {
- 'var': 'jabber:iq:version'});
+ stanza = stx`
+
+
+
+
+
+
+
+
+
+
+
+ `;
_converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
await u.waitUntil(function () {
@@ -111,35 +76,22 @@ describe("Service Discovery", function () {
stanza = IQ_stanzas.find(iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]'));
const items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
- stanza = $iq({
- 'type': 'result',
- 'from': 'montague.lit',
- 'to': 'romeo@montague.lit/orchard',
- 'id': items_IQ_id
- }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
- .c('item', {
- 'jid': 'people.shakespeare.lit',
- 'name': 'Directory of Characters'}).up()
- .c('item', {
- 'jid': 'plays.shakespeare.lit',
- 'name': 'Play-Specific Chatrooms'}).up()
- .c('item', {
- 'jid': 'words.shakespeare.lit',
- 'name': 'Gateway to Marlowe IM'}).up()
- .c('item', {
- 'jid': 'montague.lit',
- 'node': 'books',
- 'name': 'Books by and about Shakespeare'}).up()
- .c('item', {
- 'node': 'montague.lit',
- 'name': 'Wear your literary taste with pride'}).up()
- .c('item', {
- 'jid': 'montague.lit',
- 'node': 'music',
- 'name': 'Music from the time of Shakespeare'
- });
- _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
+ _converse.api.connection.get()._dataRecv(mock.createRequest(stx`
+
+
+
+
+
+
+
+
+
+ `));
const entities = await _converse.api.disco.entities.get()
expect(entities.length).toBe(5); // We have an extra entity, which is the user's JID
diff --git a/src/headless/plugins/disco/utils.js b/src/headless/plugins/disco/utils.js
index 60813edafd..0c9ebad3a2 100644
--- a/src/headless/plugins/disco/utils.js
+++ b/src/headless/plugins/disco/utils.js
@@ -7,6 +7,9 @@ import { createStore } from '../../utils/storage.js';
const { Strophe, $iq } = converse.env;
+/**
+ * @param {Element} stanza
+ */
function onDiscoInfoRequest (stanza) {
const node = stanza.getElementsByTagName('query')[0].getAttribute('node');
const attrs = {xmlns: Strophe.NS.DISCO_INFO};
diff --git a/src/headless/plugins/muc/tests/messages.js b/src/headless/plugins/muc/tests/messages.js
index 35727d14dd..8edbe2e190 100644
--- a/src/headless/plugins/muc/tests/messages.js
+++ b/src/headless/plugins/muc/tests/messages.js
@@ -96,8 +96,12 @@ describe("A MUC message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const impersonated_jid = `${muc_jid}/alice`;
- const received_stanza = u.toStanza(`
-
+ const received_stanza = stx`
+
Yet I should kill thee with much cherishing.