Move disco plugin into folder and import lodash utilities separately
This commit is contained in:
parent
f283dd848e
commit
7b7ec45db8
|
@ -27,7 +27,6 @@ module.exports = function(config) {
|
|||
|
||||
{ pattern: "spec/converse.js", type: 'module' },
|
||||
{ pattern: "spec/corrections.js", type: 'module' },
|
||||
{ pattern: "spec/disco.js", type: 'module' },
|
||||
{ pattern: "spec/emojis.js", type: 'module' },
|
||||
{ pattern: "spec/eventemitter.js", type: 'module' },
|
||||
{ pattern: "spec/http-file-upload.js", type: 'module' },
|
||||
|
@ -44,6 +43,7 @@ module.exports = function(config) {
|
|||
{ pattern: "spec/utils.js", type: 'module' },
|
||||
{ pattern: "spec/xmppstatus.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/muc/tests/affiliations.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/tests/disco.js", type: 'module' },
|
||||
{ pattern: "src/plugins/bookmark-views/tests/bookmarks.js", type: 'module' },
|
||||
{ pattern: "src/plugins/chatview/tests/chatbox.js", type: 'module' },
|
||||
{ pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' },
|
||||
|
|
68
package-lock.json
generated
68
package-lock.json
generated
|
@ -2837,10 +2837,10 @@
|
|||
}
|
||||
},
|
||||
"skeletor.js": {
|
||||
"version": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"from": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"version": "github:skeletorjs/skeletor#482acb03ff4ec92a5cabccf023294912a574132c",
|
||||
"from": "github:skeletorjs/skeletor#482acb03ff4ec92a5cabccf023294912a574132c",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
"lodash-es": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"strophe.js": {
|
||||
|
@ -4495,9 +4495,9 @@
|
|||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz",
|
||||
"integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.1.0.tgz",
|
||||
"integrity": "sha512-Z9fDZVbGj4dFLErEoXUSuZhk3wJ8KVGnbrUwoPijsQ9EyNwOeQ+U2jSqaHUz8WtgIWf0aeO59oJyhMpWCKaabg==",
|
||||
"dev": true
|
||||
},
|
||||
"@octokit/plugin-enterprise-rest": {
|
||||
|
@ -4554,18 +4554,16 @@
|
|||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz",
|
||||
"integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==",
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz",
|
||||
"integrity": "sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"deprecation": "^2.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"once": "^1.4.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -4641,9 +4639,9 @@
|
|||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz",
|
||||
"integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==",
|
||||
"version": "6.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.1.tgz",
|
||||
"integrity": "sha512-UF/PL0y4SKGx/p1azFf7e6j9lB78tVwAFvnHtslzOJ6VipshYks74qm9jjTEDlCyaTmbhbk2h3Run5l0CtCF6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@octokit/openapi-types": "^6.0.0"
|
||||
|
@ -5979,9 +5977,9 @@
|
|||
}
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.0.tgz",
|
||||
"integrity": "sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz",
|
||||
"integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw==",
|
||||
"dev": true
|
||||
},
|
||||
"better-assert": {
|
||||
|
@ -9795,9 +9793,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"filesize": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
||||
"integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg=="
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.2.6.tgz",
|
||||
"integrity": "sha512-329LZkP3cIi17Eha17gaMEtgl1IJJG/zmv5NIjk6BECybiO+88D970mp2ke7U96DT0h3NT2wimXo/XrdL+7qbQ=="
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
|
@ -13635,9 +13633,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"map-obj": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.0.tgz",
|
||||
"integrity": "sha512-NAq0fCmZYGz9UFEQyndp7sisrow4GroyGeKluyKC/chuITZsPyOyC1UJZPJlVFImhXdROIP5xqouRLThT3BbpQ==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz",
|
||||
"integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"map-visit": {
|
||||
|
@ -13837,9 +13835,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-package-data": {
|
||||
|
@ -18640,9 +18638,9 @@
|
|||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
|
||||
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz",
|
||||
"integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==",
|
||||
"dev": true
|
||||
},
|
||||
"object-is": {
|
||||
|
@ -20813,9 +20811,9 @@
|
|||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.6",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz",
|
||||
"integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==",
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
|
@ -22185,9 +22183,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
||||
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import "./plugins/caps.js"; // XEP-0115 Entity Capabilities
|
|||
import "./plugins/carbons.js"; // XEP-0280 Message Carbons
|
||||
import "./plugins/chat/index.js"; // RFC-6121 Instant messaging
|
||||
import "./plugins/chatboxes/index.js";
|
||||
import "./plugins/disco.js"; // XEP-0030 Service discovery
|
||||
import "./plugins/disco/index.js"; // XEP-0030 Service discovery
|
||||
import "./plugins/headlines.js"; // Support for headline messages
|
||||
import "./plugins/mam/index.js"; // XEP-0313 Message Archive Management
|
||||
import "./plugins/muc/index.js"; // XEP-0045 Multi-user chat
|
||||
|
|
|
@ -1,813 +0,0 @@
|
|||
/**
|
||||
* @module converse-disco
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Converse plugin which add support for XEP-0030: Service Discovery
|
||||
*/
|
||||
import log from "../log.js";
|
||||
import sizzle from "sizzle";
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { _converse, api, converse } from "../core.js";
|
||||
import { isObject } from "lodash-es";
|
||||
|
||||
const { Strophe, $iq, utils } = converse.env;
|
||||
|
||||
converse.plugins.add('converse-disco', {
|
||||
|
||||
initialize () {
|
||||
/* The initialize function gets called as soon as the plugin is
|
||||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
|
||||
// Promises exposed by this plugin
|
||||
api.promises.add('discoInitialized');
|
||||
api.promises.add('streamFeaturesAdded');
|
||||
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @namespace _converse.DiscoEntity
|
||||
* @memberOf _converse
|
||||
*/
|
||||
_converse.DiscoEntity = Model.extend({
|
||||
/* A Disco Entity is a JID addressable entity that can be queried
|
||||
* for features.
|
||||
*
|
||||
* See XEP-0030: https://xmpp.org/extensions/xep-0030.html
|
||||
*/
|
||||
idAttribute: 'jid',
|
||||
|
||||
initialize (attrs, options) {
|
||||
this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
|
||||
|
||||
this.dataforms = new Collection();
|
||||
let id = `converse.dataforms-${this.get('jid')}`;
|
||||
this.dataforms.browserStorage = _converse.createStore(id, 'session');
|
||||
|
||||
this.features = new Collection();
|
||||
id = `converse.features-${this.get('jid')}`;
|
||||
this.features.browserStorage = _converse.createStore(id, 'session');
|
||||
this.listenTo(this.features, 'add', this.onFeatureAdded)
|
||||
|
||||
this.fields = new Collection();
|
||||
id = `converse.fields-${this.get('jid')}`;
|
||||
this.fields.browserStorage = _converse.createStore(id, 'session');
|
||||
this.listenTo(this.fields, 'add', this.onFieldAdded)
|
||||
|
||||
this.identities = new Collection();
|
||||
id = `converse.identities-${this.get('jid')}`;
|
||||
this.identities.browserStorage = _converse.createStore(id, 'session');
|
||||
this.fetchFeatures(options);
|
||||
|
||||
this.items = new _converse.DiscoEntities();
|
||||
id = `converse.disco-items-${this.get('jid')}`;
|
||||
this.items.browserStorage = _converse.createStore(id, 'session');
|
||||
this.items.fetch();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise which resolves with a map indicating
|
||||
* whether a given identity is provided by this entity.
|
||||
* @private
|
||||
* @method _converse.DiscoEntity#getIdentity
|
||||
* @param { String } category - The identity category
|
||||
* @param { String } type - The identity type
|
||||
*/
|
||||
async getIdentity (category, type) {
|
||||
await this.waitUntilFeaturesDiscovered;
|
||||
return this.identities.findWhere({
|
||||
'category': category,
|
||||
'type': type
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise which resolves with a map indicating
|
||||
* whether a given feature is supported.
|
||||
* @private
|
||||
* @method _converse.DiscoEntity#hasFeature
|
||||
* @param { String } feature - The feature that might be supported.
|
||||
*/
|
||||
async hasFeature (feature) {
|
||||
await this.waitUntilFeaturesDiscovered
|
||||
if (this.features.findWhere({'var': feature})) {
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
onFeatureAdded (feature) {
|
||||
feature.entity = this;
|
||||
/**
|
||||
* Triggered when Converse has learned of a service provided by the XMPP server.
|
||||
* See XEP-0030.
|
||||
* @event _converse#serviceDiscovered
|
||||
* @type { Model }
|
||||
* @example _converse.api.listen.on('featuresDiscovered', feature => { ... });
|
||||
*/
|
||||
api.trigger('serviceDiscovered', feature);
|
||||
},
|
||||
|
||||
onFieldAdded (field) {
|
||||
field.entity = this;
|
||||
/**
|
||||
* Triggered when Converse has learned of a disco extension field.
|
||||
* See XEP-0030.
|
||||
* @event _converse#discoExtensionFieldDiscovered
|
||||
* @example _converse.api.listen.on('discoExtensionFieldDiscovered', () => { ... });
|
||||
*/
|
||||
api.trigger('discoExtensionFieldDiscovered', field);
|
||||
},
|
||||
|
||||
async fetchFeatures (options) {
|
||||
if (options.ignore_cache) {
|
||||
this.queryInfo();
|
||||
} else {
|
||||
const store_id = this.features.browserStorage.name;
|
||||
const result = await this.features.browserStorage.store.getItem(store_id);
|
||||
if (result && result.length === 0 || result === null) {
|
||||
this.queryInfo();
|
||||
} else {
|
||||
this.features.fetch({
|
||||
add: true,
|
||||
success: () => {
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
this.trigger('featuresDiscovered');
|
||||
}
|
||||
});
|
||||
this.identities.fetch({add: true});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async queryInfo () {
|
||||
let stanza;
|
||||
try {
|
||||
stanza = await api.disco.info(this.get('jid'), null);
|
||||
} catch (iq) {
|
||||
iq === null ? log.error(`Timeout for disco#info query for ${this.get('jid')}`) : log.error(iq);
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
return;
|
||||
}
|
||||
this.onInfo(stanza);
|
||||
},
|
||||
|
||||
onDiscoItems (stanza) {
|
||||
sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza).forEach(item => {
|
||||
if (item.getAttribute("node")) {
|
||||
// XXX: Ignore nodes for now.
|
||||
// See: https://xmpp.org/extensions/xep-0030.html#items-nodes
|
||||
return;
|
||||
}
|
||||
const jid = item.getAttribute('jid');
|
||||
if (this.items.get(jid) === undefined) {
|
||||
const entity = _converse.disco_entities.get(jid);
|
||||
if (entity) {
|
||||
this.items.add(entity);
|
||||
} else {
|
||||
this.items.create({'jid': jid});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async queryForItems () {
|
||||
if (this.identities.where({'category': 'server'}).length === 0) {
|
||||
// Don't fetch features and items if this is not a
|
||||
// server or a conference component.
|
||||
return;
|
||||
}
|
||||
const stanza = await api.disco.items(this.get('jid'));
|
||||
this.onDiscoItems(stanza);
|
||||
},
|
||||
|
||||
onInfo (stanza) {
|
||||
Array.from(stanza.querySelectorAll('identity')).forEach(identity => {
|
||||
this.identities.create({
|
||||
'category': identity.getAttribute('category'),
|
||||
'type': identity.getAttribute('type'),
|
||||
'name': identity.getAttribute('name')
|
||||
});
|
||||
});
|
||||
|
||||
sizzle(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza).forEach(form => {
|
||||
const data = {};
|
||||
sizzle('field', form).forEach(field => {
|
||||
data[field.getAttribute('var')] = {
|
||||
'value': field.querySelector('value')?.textContent,
|
||||
'type': field.getAttribute('type')
|
||||
};
|
||||
});
|
||||
this.dataforms.create(data);
|
||||
});
|
||||
|
||||
if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
|
||||
this.queryForItems();
|
||||
}
|
||||
Array.from(stanza.querySelectorAll('feature')).forEach(feature => {
|
||||
this.features.create({
|
||||
'var': feature.getAttribute('var'),
|
||||
'from': stanza.getAttribute('from')
|
||||
});
|
||||
});
|
||||
|
||||
// XEP-0128 Service Discovery Extensions
|
||||
sizzle('x[type="result"][xmlns="jabber:x:data"] field', stanza).forEach(field => {
|
||||
this.fields.create({
|
||||
'var': field.getAttribute('var'),
|
||||
'value': field.querySelector('value')?.textContent,
|
||||
'from': stanza.getAttribute('from')
|
||||
});
|
||||
});
|
||||
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
this.trigger('featuresDiscovered');
|
||||
}
|
||||
});
|
||||
|
||||
_converse.DiscoEntities = Collection.extend({
|
||||
model: _converse.DiscoEntity,
|
||||
|
||||
fetchEntities () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fetch({
|
||||
add: true,
|
||||
success: resolve,
|
||||
error (m, e) {
|
||||
log.error(e);
|
||||
reject (new Error("Could not fetch disco entities"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addClientFeatures () {
|
||||
// See https://xmpp.org/registrar/disco-categories.html
|
||||
api.disco.own.identities.add('client', 'web', 'Converse');
|
||||
|
||||
api.disco.own.features.add(Strophe.NS.CHATSTATES);
|
||||
api.disco.own.features.add(Strophe.NS.DISCO_INFO);
|
||||
api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support
|
||||
if (api.settings.get("message_carbons")) {
|
||||
api.disco.own.features.add(Strophe.NS.CARBONS);
|
||||
}
|
||||
/**
|
||||
* Triggered in converse-disco once the core disco features of
|
||||
* Converse have been added.
|
||||
* @event _converse#addClientFeatures
|
||||
* @example _converse.api.listen.on('addClientFeatures', () => { ... });
|
||||
*/
|
||||
api.trigger('addClientFeatures');
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
function initStreamFeatures () {
|
||||
// Initialize the stream_features collection, and if we're
|
||||
// re-attaching to a pre-existing BOSH session, we restore the
|
||||
// features from cache.
|
||||
// Otherwise the features will be created once we've received them
|
||||
// from the server (see populateStreamFeatures).
|
||||
if (!_converse.stream_features) {
|
||||
const bare_jid = Strophe.getBareJidFromJid(_converse.jid);
|
||||
const id = `converse.stream-features-${bare_jid}`;
|
||||
api.promises.add('streamFeaturesAdded');
|
||||
_converse.stream_features = new Collection();
|
||||
_converse.stream_features.browserStorage = _converse.createStore(id, "session");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function populateStreamFeatures () {
|
||||
// Strophe.js sets the <stream:features> element on the
|
||||
// Strophe.Connection instance (_converse.connection).
|
||||
//
|
||||
// Once this is done, we populate the _converse.stream_features collection
|
||||
// and trigger streamFeaturesAdded.
|
||||
initStreamFeatures();
|
||||
Array.from(_converse.connection.features.childNodes).forEach(feature => {
|
||||
_converse.stream_features.create({
|
||||
'name': feature.nodeName,
|
||||
'xmlns': feature.getAttribute('xmlns')
|
||||
});
|
||||
});
|
||||
notifyStreamFeaturesAdded();
|
||||
}
|
||||
|
||||
|
||||
function notifyStreamFeaturesAdded () {
|
||||
/**
|
||||
* Triggered as soon as the stream features are known.
|
||||
* If you want to check whether a stream feature is supported before proceeding,
|
||||
* then you'll first want to wait for this event.
|
||||
* @event _converse#streamFeaturesAdded
|
||||
* @example _converse.api.listen.on('streamFeaturesAdded', () => { ... });
|
||||
*/
|
||||
api.trigger('streamFeaturesAdded');
|
||||
}
|
||||
|
||||
|
||||
const plugin = this;
|
||||
plugin._identities = [];
|
||||
plugin._features = [];
|
||||
|
||||
function onDiscoInfoRequest (stanza) {
|
||||
const node = stanza.getElementsByTagName('query')[0].getAttribute('node');
|
||||
const attrs = {xmlns: Strophe.NS.DISCO_INFO};
|
||||
if (node) { attrs.node = node; }
|
||||
|
||||
const iqresult = $iq({'type': 'result', 'id': stanza.getAttribute('id')});
|
||||
const from = stanza.getAttribute('from');
|
||||
if (from !== null) {
|
||||
iqresult.attrs({'to': from});
|
||||
}
|
||||
iqresult.c('query', attrs);
|
||||
plugin._identities.forEach(identity => {
|
||||
const attrs = {
|
||||
'category': identity.category,
|
||||
'type': identity.type
|
||||
};
|
||||
if (identity.name) {
|
||||
attrs.name = identity.name;
|
||||
}
|
||||
if (identity.lang) {
|
||||
attrs['xml:lang'] = identity.lang;
|
||||
}
|
||||
iqresult.c('identity', attrs).up();
|
||||
});
|
||||
plugin._features.forEach(feature => iqresult.c('feature', {'var': feature}).up());
|
||||
api.send(iqresult.tree());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async function initializeDisco () {
|
||||
addClientFeatures();
|
||||
_converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
|
||||
|
||||
_converse.disco_entities = new _converse.DiscoEntities();
|
||||
const id = `converse.disco-entities-${_converse.bare_jid}`;
|
||||
_converse.disco_entities.browserStorage = _converse.createStore(id, 'session');
|
||||
const collection = await _converse.disco_entities.fetchEntities();
|
||||
if (collection.length === 0 || !collection.get(_converse.domain)) {
|
||||
// If we don't have an entity for our own XMPP server,
|
||||
// create one.
|
||||
_converse.disco_entities.create({'jid': _converse.domain});
|
||||
}
|
||||
/**
|
||||
* Triggered once the `converse-disco` plugin has been initialized and the
|
||||
* `_converse.disco_entities` collection will be available and populated with at
|
||||
* least the service discovery features of the user's own server.
|
||||
* @event _converse#discoInitialized
|
||||
* @example _converse.api.listen.on('discoInitialized', () => { ... });
|
||||
*/
|
||||
api.trigger('discoInitialized');
|
||||
}
|
||||
|
||||
/******************** Event Handlers ********************/
|
||||
|
||||
api.listen.on('userSessionInitialized', async () => {
|
||||
initStreamFeatures();
|
||||
if (_converse.connfeedback.get('connection_status') === Strophe.Status.ATTACHED) {
|
||||
// When re-attaching to a BOSH session, we fetch the stream features from the cache.
|
||||
await new Promise((success, error) => _converse.stream_features.fetch({ success, error }));
|
||||
notifyStreamFeaturesAdded();
|
||||
}
|
||||
});
|
||||
api.listen.on('beforeResourceBinding', populateStreamFeatures);
|
||||
|
||||
api.listen.on('reconnected', initializeDisco);
|
||||
api.listen.on('connected', initializeDisco);
|
||||
|
||||
api.listen.on('beforeTearDown', async () => {
|
||||
api.promises.add('streamFeaturesAdded')
|
||||
if (_converse.stream_features) {
|
||||
await _converse.stream_features.clearStore();
|
||||
delete _converse.stream_features;
|
||||
}
|
||||
});
|
||||
|
||||
api.listen.on('clearSession', () => {
|
||||
if (_converse.shouldClearCache() && _converse.disco_entities) {
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.features.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.identities.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.dataforms.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.fields.clearStore());
|
||||
_converse.disco_entities.clearStore();
|
||||
delete _converse.disco_entities;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/************************ API ************************/
|
||||
|
||||
Object.assign(api, {
|
||||
/**
|
||||
* The XEP-0030 service discovery API
|
||||
*
|
||||
* This API lets you discover information about entities on the
|
||||
* XMPP network.
|
||||
*
|
||||
* @namespace api.disco
|
||||
* @memberOf api
|
||||
*/
|
||||
disco: {
|
||||
/**
|
||||
* @namespace api.disco.stream
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
stream: {
|
||||
/**
|
||||
* @method api.disco.stream.getFeature
|
||||
* @param {String} name The feature name
|
||||
* @param {String} xmlns The XML namespace
|
||||
* @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
|
||||
*/
|
||||
async getFeature (name, xmlns) {
|
||||
await api.waitUntil('streamFeaturesAdded');
|
||||
if (!name || !xmlns) {
|
||||
throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
|
||||
}
|
||||
if (_converse.stream_features === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to get feature ${name} ${xmlns} but _converse.stream_features has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
return _converse.stream_features.findWhere({'name': name, 'xmlns': xmlns});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.own
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
own: {
|
||||
/**
|
||||
* @namespace api.disco.own.identities
|
||||
* @memberOf api.disco.own
|
||||
*/
|
||||
identities: {
|
||||
/**
|
||||
* Lets you add new identities for this client (i.e. instance of Converse)
|
||||
* @method api.disco.own.identities.add
|
||||
*
|
||||
* @param {String} category - server, client, gateway, directory, etc.
|
||||
* @param {String} type - phone, pc, web, etc.
|
||||
* @param {String} name - "Converse"
|
||||
* @param {String} lang - en, el, de, etc.
|
||||
*
|
||||
* @example _converse.api.disco.own.identities.clear();
|
||||
*/
|
||||
add (category, type, name, lang) {
|
||||
for (var i=0; i<plugin._identities.length; i++) {
|
||||
if (plugin._identities[i].category == category &&
|
||||
plugin._identities[i].type == type &&
|
||||
plugin._identities[i].name == name &&
|
||||
plugin._identities[i].lang == lang) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
plugin._identities.push({category: category, type: type, name: name, lang: lang});
|
||||
},
|
||||
/**
|
||||
* Clears all previously registered identities.
|
||||
* @method api.disco.own.identities.clear
|
||||
* @example _converse.api.disco.own.identities.clear();
|
||||
*/
|
||||
clear () {
|
||||
plugin._identities = []
|
||||
},
|
||||
/**
|
||||
* Returns all of the identities registered for this client
|
||||
* (i.e. instance of Converse).
|
||||
* @method api.disco.identities.get
|
||||
* @example const identities = api.disco.own.identities.get();
|
||||
*/
|
||||
get () {
|
||||
return plugin._identities;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.own.features
|
||||
* @memberOf api.disco.own
|
||||
*/
|
||||
features: {
|
||||
/**
|
||||
* Lets you register new disco features for this client (i.e. instance of Converse)
|
||||
* @method api.disco.own.features.add
|
||||
* @param {String} name - e.g. http://jabber.org/protocol/caps
|
||||
* @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
|
||||
*/
|
||||
add (name) {
|
||||
for (var i=0; i<plugin._features.length; i++) {
|
||||
if (plugin._features[i] == name) { return false; }
|
||||
}
|
||||
plugin._features.push(name);
|
||||
},
|
||||
/**
|
||||
* Clears all previously registered features.
|
||||
* @method api.disco.own.features.clear
|
||||
* @example _converse.api.disco.own.features.clear();
|
||||
*/
|
||||
clear () {
|
||||
plugin._features = []
|
||||
},
|
||||
/**
|
||||
* Returns all of the features registered for this client (i.e. instance of Converse).
|
||||
* @method api.disco.own.features.get
|
||||
* @example const features = api.disco.own.features.get();
|
||||
*/
|
||||
get () {
|
||||
return plugin._features;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for information about an XMPP entity
|
||||
*
|
||||
* @method api.disco.info
|
||||
* @param {string} jid The Jabber ID of the entity to query
|
||||
* @param {string} [node] A specific node identifier associated with the JID
|
||||
* @returns {promise} Promise which resolves once we have a result from the server.
|
||||
*/
|
||||
info (jid, node) {
|
||||
const attrs = {xmlns: Strophe.NS.DISCO_INFO};
|
||||
if (node) {
|
||||
attrs.node = node;
|
||||
}
|
||||
const info = $iq({
|
||||
'from': _converse.connection.jid,
|
||||
'to':jid,
|
||||
'type':'get'
|
||||
}).c('query', attrs);
|
||||
return api.sendIQ(info);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for items associated with an XMPP entity
|
||||
*
|
||||
* @method api.disco.items
|
||||
* @param {string} jid The Jabber ID of the entity to query for items
|
||||
* @param {string} [node] A specific node identifier associated with the JID
|
||||
* @returns {promise} Promise which resolves once we have a result from the server.
|
||||
*/
|
||||
items (jid, node) {
|
||||
const attrs = {'xmlns': Strophe.NS.DISCO_ITEMS};
|
||||
if (node) {
|
||||
attrs.node = node;
|
||||
}
|
||||
return api.sendIQ(
|
||||
$iq({
|
||||
'from': _converse.connection.jid,
|
||||
'to':jid,
|
||||
'type':'get'
|
||||
}).c('query', attrs)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Namespace for methods associated with disco entities
|
||||
*
|
||||
* @namespace api.disco.entities
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
entities: {
|
||||
/**
|
||||
* Get the corresponding `DiscoEntity` instance.
|
||||
*
|
||||
* @method api.disco.entities.get
|
||||
* @param {string} jid The Jabber ID of the entity
|
||||
* @param {boolean} [create] Whether the entity should be created if it doesn't exist.
|
||||
* @example _converse.api.disco.entities.get(jid);
|
||||
*/
|
||||
async get (jid, create=false) {
|
||||
await api.waitUntil('discoInitialized');
|
||||
if (!jid) {
|
||||
return _converse.disco_entities;
|
||||
}
|
||||
if (_converse.disco_entities === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to look up entity ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
const entity = _converse.disco_entities.get(jid);
|
||||
if (entity || !create) {
|
||||
return entity;
|
||||
}
|
||||
return api.disco.entities.create(jid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new disco entity. It's identity and features
|
||||
* will automatically be fetched from cache or from the
|
||||
* XMPP server.
|
||||
*
|
||||
* Fetching from cache can be disabled by passing in
|
||||
* `ignore_cache: true` in the options parameter.
|
||||
*
|
||||
* @method api.disco.entities.create
|
||||
* @param {string} jid The Jabber ID of the entity
|
||||
* @param {object} [options] Additional options
|
||||
* @param {boolean} [options.ignore_cache]
|
||||
* If true, fetch all features from the XMPP server instead of restoring them from cache
|
||||
* @example _converse.api.disco.entities.create(jid, {'ignore_cache': true});
|
||||
*/
|
||||
create (jid, options) {
|
||||
return _converse.disco_entities.create({'jid': jid}, options);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.features
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
features: {
|
||||
/**
|
||||
* Return a given feature of a disco entity
|
||||
*
|
||||
* @method api.disco.features.get
|
||||
* @param {string} feature The feature that might be
|
||||
* supported. In the XML stanza, this is the `var`
|
||||
* attribute of the `<feature>` element. For
|
||||
* example: `http://jabber.org/protocol/muc`
|
||||
* @param {string} jid The JID of the entity
|
||||
* (and its associated items) which should be queried
|
||||
* @returns {promise} A promise which resolves with a list containing
|
||||
* _converse.Entity instances representing the entity
|
||||
* itself or those items associated with the entity if
|
||||
* they support the given feature.
|
||||
* @example
|
||||
* api.disco.features.get(Strophe.NS.MAM, _converse.bare_jid);
|
||||
*/
|
||||
async get (feature, jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
|
||||
if (_converse.disco_entities === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to get feature ${feature} for ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
const promises = [...entity.items.map(i => i.hasFeature(feature)), entity.hasFeature(feature)];
|
||||
const result = await Promise.all(promises);
|
||||
return result.filter(isObject);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to determine whether an entity supports a given feature.
|
||||
*
|
||||
* @method api.disco.supports
|
||||
* @param {string} feature The feature that might be
|
||||
* supported. In the XML stanza, this is the `var`
|
||||
* attribute of the `<feature>` element. For
|
||||
* example: `http://jabber.org/protocol/muc`
|
||||
* @param {string} jid The JID of the entity
|
||||
* (and its associated items) which should be queried
|
||||
* @returns {promise} A promise which resolves with `true` or `false`.
|
||||
* @example
|
||||
* if (await api.disco.supports(Strophe.NS.MAM, _converse.bare_jid)) {
|
||||
* // The feature is supported
|
||||
* } else {
|
||||
* // The feature is not supported
|
||||
* }
|
||||
*/
|
||||
async supports (feature, jid) {
|
||||
const features = await api.disco.features.get(feature, jid);
|
||||
return features.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the features, fields and identities associated with a
|
||||
* disco entity by refetching them from the server
|
||||
* @method api.disco.refresh
|
||||
* @param {string} jid The JID of the entity whose features are refreshed.
|
||||
* @returns {promise} A promise which resolves once the features have been refreshed
|
||||
* @example
|
||||
* await api.disco.refresh('room@conference.example.org');
|
||||
*/
|
||||
async refresh (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.refresh: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid);
|
||||
if (entity) {
|
||||
entity.features.reset();
|
||||
entity.fields.reset();
|
||||
entity.identities.reset();
|
||||
if (!entity.waitUntilFeaturesDiscovered.isPending) {
|
||||
entity.waitUntilFeaturesDiscovered = utils.getResolveablePromise()
|
||||
}
|
||||
entity.queryInfo();
|
||||
} else {
|
||||
// Create it if it doesn't exist
|
||||
entity = await api.disco.entities.create(jid, {'ignore_cache': true});
|
||||
}
|
||||
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
|
||||
*
|
||||
* @method api.disco.getFeatures
|
||||
* @param {string} jid The JID of the entity whose features are returned.
|
||||
* @returns {promise} A promise which resolves with the returned features
|
||||
* @example
|
||||
* const features = await api.disco.getFeatures('room@conference.example.org');
|
||||
*/
|
||||
async getFeatures (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.getFeatures: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
return entity.features;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return all the service discovery extensions fields
|
||||
* associated with an entity.
|
||||
*
|
||||
* See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html)
|
||||
*
|
||||
* @method api.disco.getFields
|
||||
* @param {string} jid The JID of the entity whose fields are returned.
|
||||
* @example
|
||||
* const fields = await api.disco.getFields('room@conference.example.org');
|
||||
*/
|
||||
async getFields (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.getFields: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
return entity.fields;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the identity (with the given category and type) for a given disco entity.
|
||||
*
|
||||
* For example, when determining support for PEP (personal eventing protocol), you
|
||||
* want to know whether the user's own JID has an identity with
|
||||
* `category='pubsub'` and `type='pep'` as explained in this section of
|
||||
* XEP-0163: https://xmpp.org/extensions/xep-0163.html#support
|
||||
*
|
||||
* @method api.disco.getIdentity
|
||||
* @param {string} The identity category.
|
||||
* In the XML stanza, this is the `category`
|
||||
* attribute of the `<identity>` element.
|
||||
* For example: 'pubsub'
|
||||
* @param {string} type The identity type.
|
||||
* In the XML stanza, this is the `type`
|
||||
* attribute of the `<identity>` element.
|
||||
* For example: 'pep'
|
||||
* @param {string} jid The JID of the entity which might have the identity
|
||||
* @returns {promise} A promise which resolves with a map indicating
|
||||
* whether an identity with a given type is provided by the entity.
|
||||
* @example
|
||||
* api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then(
|
||||
* function (identity) {
|
||||
* if (identity) {
|
||||
* // The entity DOES have this identity
|
||||
* } else {
|
||||
* // The entity DOES NOT have this identity
|
||||
* }
|
||||
* }
|
||||
* ).catch(e => log.error(e));
|
||||
*/
|
||||
async getIdentity (category, type, jid) {
|
||||
const e = await api.disco.entities.get(jid, true);
|
||||
if (e === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to look up category ${category} for ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
return e.getIdentity(category, type);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
413
src/headless/plugins/disco/api.js
Normal file
413
src/headless/plugins/disco/api.js
Normal file
|
@ -0,0 +1,413 @@
|
|||
import { _converse, api, converse } from "@converse/headless/core.js";
|
||||
import isObject from "lodash-es/isObject";
|
||||
import log from "@converse/headless/log.js";
|
||||
|
||||
const { Strophe, $iq, utils } = converse.env;
|
||||
|
||||
|
||||
export default {
|
||||
/**
|
||||
* The XEP-0030 service discovery API
|
||||
*
|
||||
* This API lets you discover information about entities on the
|
||||
* XMPP network.
|
||||
*
|
||||
* @namespace api.disco
|
||||
* @memberOf api
|
||||
*/
|
||||
disco: {
|
||||
/**
|
||||
* @namespace api.disco.stream
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
stream: {
|
||||
/**
|
||||
* @method api.disco.stream.getFeature
|
||||
* @param {String} name The feature name
|
||||
* @param {String} xmlns The XML namespace
|
||||
* @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
|
||||
*/
|
||||
async getFeature (name, xmlns) {
|
||||
await api.waitUntil('streamFeaturesAdded');
|
||||
if (!name || !xmlns) {
|
||||
throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
|
||||
}
|
||||
if (_converse.stream_features === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to get feature ${name} ${xmlns} but _converse.stream_features has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
return _converse.stream_features.findWhere({'name': name, 'xmlns': xmlns});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.own
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
own: {
|
||||
/**
|
||||
* @namespace api.disco.own.identities
|
||||
* @memberOf api.disco.own
|
||||
*/
|
||||
identities: {
|
||||
/**
|
||||
* Lets you add new identities for this client (i.e. instance of Converse)
|
||||
* @method api.disco.own.identities.add
|
||||
*
|
||||
* @param {String} category - server, client, gateway, directory, etc.
|
||||
* @param {String} type - phone, pc, web, etc.
|
||||
* @param {String} name - "Converse"
|
||||
* @param {String} lang - en, el, de, etc.
|
||||
*
|
||||
* @example _converse.api.disco.own.identities.clear();
|
||||
*/
|
||||
add (category, type, name, lang) {
|
||||
for (var i=0; i<_converse.disco._identities.length; i++) {
|
||||
if (_converse.disco._identities[i].category == category &&
|
||||
_converse.disco._identities[i].type == type &&
|
||||
_converse.disco._identities[i].name == name &&
|
||||
_converse.disco._identities[i].lang == lang) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_converse.disco._identities.push({category: category, type: type, name: name, lang: lang});
|
||||
},
|
||||
/**
|
||||
* Clears all previously registered identities.
|
||||
* @method api.disco.own.identities.clear
|
||||
* @example _converse.api.disco.own.identities.clear();
|
||||
*/
|
||||
clear () {
|
||||
_converse.disco._identities = []
|
||||
},
|
||||
/**
|
||||
* Returns all of the identities registered for this client
|
||||
* (i.e. instance of Converse).
|
||||
* @method api.disco.identities.get
|
||||
* @example const identities = api.disco.own.identities.get();
|
||||
*/
|
||||
get () {
|
||||
return _converse.disco._identities;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.own.features
|
||||
* @memberOf api.disco.own
|
||||
*/
|
||||
features: {
|
||||
/**
|
||||
* Lets you register new disco features for this client (i.e. instance of Converse)
|
||||
* @method api.disco.own.features.add
|
||||
* @param {String} name - e.g. http://jabber.org/protocol/caps
|
||||
* @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
|
||||
*/
|
||||
add (name) {
|
||||
for (var i=0; i<_converse.disco._features.length; i++) {
|
||||
if (_converse.disco._features[i] == name) { return false; }
|
||||
}
|
||||
_converse.disco._features.push(name);
|
||||
},
|
||||
/**
|
||||
* Clears all previously registered features.
|
||||
* @method api.disco.own.features.clear
|
||||
* @example _converse.api.disco.own.features.clear();
|
||||
*/
|
||||
clear () {
|
||||
_converse.disco._features = []
|
||||
},
|
||||
/**
|
||||
* Returns all of the features registered for this client (i.e. instance of Converse).
|
||||
* @method api.disco.own.features.get
|
||||
* @example const features = api.disco.own.features.get();
|
||||
*/
|
||||
get () {
|
||||
return _converse.disco._features;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for information about an XMPP entity
|
||||
*
|
||||
* @method api.disco.info
|
||||
* @param {string} jid The Jabber ID of the entity to query
|
||||
* @param {string} [node] A specific node identifier associated with the JID
|
||||
* @returns {promise} Promise which resolves once we have a result from the server.
|
||||
*/
|
||||
info (jid, node) {
|
||||
const attrs = {xmlns: Strophe.NS.DISCO_INFO};
|
||||
if (node) {
|
||||
attrs.node = node;
|
||||
}
|
||||
const info = $iq({
|
||||
'from': _converse.connection.jid,
|
||||
'to':jid,
|
||||
'type':'get'
|
||||
}).c('query', attrs);
|
||||
return api.sendIQ(info);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for items associated with an XMPP entity
|
||||
*
|
||||
* @method api.disco.items
|
||||
* @param {string} jid The Jabber ID of the entity to query for items
|
||||
* @param {string} [node] A specific node identifier associated with the JID
|
||||
* @returns {promise} Promise which resolves once we have a result from the server.
|
||||
*/
|
||||
items (jid, node) {
|
||||
const attrs = {'xmlns': Strophe.NS.DISCO_ITEMS};
|
||||
if (node) {
|
||||
attrs.node = node;
|
||||
}
|
||||
return api.sendIQ(
|
||||
$iq({
|
||||
'from': _converse.connection.jid,
|
||||
'to':jid,
|
||||
'type':'get'
|
||||
}).c('query', attrs)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Namespace for methods associated with disco entities
|
||||
*
|
||||
* @namespace api.disco.entities
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
entities: {
|
||||
/**
|
||||
* Get the corresponding `DiscoEntity` instance.
|
||||
*
|
||||
* @method api.disco.entities.get
|
||||
* @param {string} jid The Jabber ID of the entity
|
||||
* @param {boolean} [create] Whether the entity should be created if it doesn't exist.
|
||||
* @example _converse.api.disco.entities.get(jid);
|
||||
*/
|
||||
async get (jid, create=false) {
|
||||
await api.waitUntil('discoInitialized');
|
||||
if (!jid) {
|
||||
return _converse.disco_entities;
|
||||
}
|
||||
if (_converse.disco_entities === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to look up entity ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
const entity = _converse.disco_entities.get(jid);
|
||||
if (entity || !create) {
|
||||
return entity;
|
||||
}
|
||||
return api.disco.entities.create(jid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new disco entity. It's identity and features
|
||||
* will automatically be fetched from cache or from the
|
||||
* XMPP server.
|
||||
*
|
||||
* Fetching from cache can be disabled by passing in
|
||||
* `ignore_cache: true` in the options parameter.
|
||||
*
|
||||
* @method api.disco.entities.create
|
||||
* @param {string} jid The Jabber ID of the entity
|
||||
* @param {object} [options] Additional options
|
||||
* @param {boolean} [options.ignore_cache]
|
||||
* If true, fetch all features from the XMPP server instead of restoring them from cache
|
||||
* @example _converse.api.disco.entities.create(jid, {'ignore_cache': true});
|
||||
*/
|
||||
create (jid, options) {
|
||||
return _converse.disco_entities.create({'jid': jid}, options);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @namespace api.disco.features
|
||||
* @memberOf api.disco
|
||||
*/
|
||||
features: {
|
||||
/**
|
||||
* Return a given feature of a disco entity
|
||||
*
|
||||
* @method api.disco.features.get
|
||||
* @param {string} feature The feature that might be
|
||||
* supported. In the XML stanza, this is the `var`
|
||||
* attribute of the `<feature>` element. For
|
||||
* example: `http://jabber.org/protocol/muc`
|
||||
* @param {string} jid The JID of the entity
|
||||
* (and its associated items) which should be queried
|
||||
* @returns {promise} A promise which resolves with a list containing
|
||||
* _converse.Entity instances representing the entity
|
||||
* itself or those items associated with the entity if
|
||||
* they support the given feature.
|
||||
* @example
|
||||
* api.disco.features.get(Strophe.NS.MAM, _converse.bare_jid);
|
||||
*/
|
||||
async get (feature, jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
|
||||
if (_converse.disco_entities === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to get feature ${feature} for ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
const promises = [...entity.items.map(i => i.hasFeature(feature)), entity.hasFeature(feature)];
|
||||
const result = await Promise.all(promises);
|
||||
return result.filter(isObject);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to determine whether an entity supports a given feature.
|
||||
*
|
||||
* @method api.disco.supports
|
||||
* @param {string} feature The feature that might be
|
||||
* supported. In the XML stanza, this is the `var`
|
||||
* attribute of the `<feature>` element. For
|
||||
* example: `http://jabber.org/protocol/muc`
|
||||
* @param {string} jid The JID of the entity
|
||||
* (and its associated items) which should be queried
|
||||
* @returns {promise} A promise which resolves with `true` or `false`.
|
||||
* @example
|
||||
* if (await api.disco.supports(Strophe.NS.MAM, _converse.bare_jid)) {
|
||||
* // The feature is supported
|
||||
* } else {
|
||||
* // The feature is not supported
|
||||
* }
|
||||
*/
|
||||
async supports (feature, jid) {
|
||||
const features = await api.disco.features.get(feature, jid);
|
||||
return features.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the features, fields and identities associated with a
|
||||
* disco entity by refetching them from the server
|
||||
* @method api.disco.refresh
|
||||
* @param {string} jid The JID of the entity whose features are refreshed.
|
||||
* @returns {promise} A promise which resolves once the features have been refreshed
|
||||
* @example
|
||||
* await api.disco.refresh('room@conference.example.org');
|
||||
*/
|
||||
async refresh (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.refresh: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid);
|
||||
if (entity) {
|
||||
entity.features.reset();
|
||||
entity.fields.reset();
|
||||
entity.identities.reset();
|
||||
if (!entity.waitUntilFeaturesDiscovered.isPending) {
|
||||
entity.waitUntilFeaturesDiscovered = utils.getResolveablePromise()
|
||||
}
|
||||
entity.queryInfo();
|
||||
} else {
|
||||
// Create it if it doesn't exist
|
||||
entity = await api.disco.entities.create(jid, {'ignore_cache': true});
|
||||
}
|
||||
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
|
||||
*
|
||||
* @method api.disco.getFeatures
|
||||
* @param {string} jid The JID of the entity whose features are returned.
|
||||
* @returns {promise} A promise which resolves with the returned features
|
||||
* @example
|
||||
* const features = await api.disco.getFeatures('room@conference.example.org');
|
||||
*/
|
||||
async getFeatures (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.getFeatures: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
return entity.features;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return all the service discovery extensions fields
|
||||
* associated with an entity.
|
||||
*
|
||||
* See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html)
|
||||
*
|
||||
* @method api.disco.getFields
|
||||
* @param {string} jid The JID of the entity whose fields are returned.
|
||||
* @example
|
||||
* const fields = await api.disco.getFields('room@conference.example.org');
|
||||
*/
|
||||
async getFields (jid) {
|
||||
if (!jid) {
|
||||
throw new TypeError('api.disco.getFields: You need to provide an entity JID');
|
||||
}
|
||||
await api.waitUntil('discoInitialized');
|
||||
let entity = await api.disco.entities.get(jid, true);
|
||||
entity = await entity.waitUntilFeaturesDiscovered;
|
||||
return entity.fields;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the identity (with the given category and type) for a given disco entity.
|
||||
*
|
||||
* For example, when determining support for PEP (personal eventing protocol), you
|
||||
* want to know whether the user's own JID has an identity with
|
||||
* `category='pubsub'` and `type='pep'` as explained in this section of
|
||||
* XEP-0163: https://xmpp.org/extensions/xep-0163.html#support
|
||||
*
|
||||
* @method api.disco.getIdentity
|
||||
* @param {string} The identity category.
|
||||
* In the XML stanza, this is the `category`
|
||||
* attribute of the `<identity>` element.
|
||||
* For example: 'pubsub'
|
||||
* @param {string} type The identity type.
|
||||
* In the XML stanza, this is the `type`
|
||||
* attribute of the `<identity>` element.
|
||||
* For example: 'pep'
|
||||
* @param {string} jid The JID of the entity which might have the identity
|
||||
* @returns {promise} A promise which resolves with a map indicating
|
||||
* whether an identity with a given type is provided by the entity.
|
||||
* @example
|
||||
* api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then(
|
||||
* function (identity) {
|
||||
* if (identity) {
|
||||
* // The entity DOES have this identity
|
||||
* } else {
|
||||
* // The entity DOES NOT have this identity
|
||||
* }
|
||||
* }
|
||||
* ).catch(e => log.error(e));
|
||||
*/
|
||||
async getIdentity (category, type, jid) {
|
||||
const e = await api.disco.entities.get(jid, true);
|
||||
if (e === undefined && !api.connection.connected()) {
|
||||
// Happens during tests when disco lookups happen asynchronously after teardown.
|
||||
const msg = `Tried to look up category ${category} for ${jid} but _converse.disco_entities has been torn down`;
|
||||
log.warn(msg);
|
||||
return;
|
||||
}
|
||||
return e.getIdentity(category, type);
|
||||
}
|
||||
}
|
||||
}
|
23
src/headless/plugins/disco/entities.js
Normal file
23
src/headless/plugins/disco/entities.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import DiscoEntity from './entity.js';
|
||||
import log from "@converse/headless/log.js";
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
|
||||
|
||||
const DiscoEntities = Collection.extend({
|
||||
model: DiscoEntity,
|
||||
|
||||
fetchEntities () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fetch({
|
||||
add: true,
|
||||
success: resolve,
|
||||
error (m, e) {
|
||||
log.error(e);
|
||||
reject (new Error("Could not fetch disco entities"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default DiscoEntities;
|
208
src/headless/plugins/disco/entity.js
Normal file
208
src/headless/plugins/disco/entity.js
Normal file
|
@ -0,0 +1,208 @@
|
|||
import log from "@converse/headless/log.js";
|
||||
import sizzle from "sizzle";
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { _converse, api, converse } from "@converse/headless/core.js";
|
||||
|
||||
const { Strophe, utils } = converse.env;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @namespace _converse.DiscoEntity
|
||||
* @memberOf _converse
|
||||
*
|
||||
* A Disco Entity is a JID addressable entity that can be queried for features.
|
||||
*
|
||||
* See XEP-0030: https://xmpp.org/extensions/xep-0030.html
|
||||
*/
|
||||
const DiscoEntity = Model.extend({
|
||||
idAttribute: 'jid',
|
||||
|
||||
initialize (attrs, options) {
|
||||
this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
|
||||
|
||||
this.dataforms = new Collection();
|
||||
let id = `converse.dataforms-${this.get('jid')}`;
|
||||
this.dataforms.browserStorage = _converse.createStore(id, 'session');
|
||||
|
||||
this.features = new Collection();
|
||||
id = `converse.features-${this.get('jid')}`;
|
||||
this.features.browserStorage = _converse.createStore(id, 'session');
|
||||
this.listenTo(this.features, 'add', this.onFeatureAdded)
|
||||
|
||||
this.fields = new Collection();
|
||||
id = `converse.fields-${this.get('jid')}`;
|
||||
this.fields.browserStorage = _converse.createStore(id, 'session');
|
||||
this.listenTo(this.fields, 'add', this.onFieldAdded)
|
||||
|
||||
this.identities = new Collection();
|
||||
id = `converse.identities-${this.get('jid')}`;
|
||||
this.identities.browserStorage = _converse.createStore(id, 'session');
|
||||
this.fetchFeatures(options);
|
||||
|
||||
this.items = new _converse.DiscoEntities();
|
||||
id = `converse.disco-items-${this.get('jid')}`;
|
||||
this.items.browserStorage = _converse.createStore(id, 'session');
|
||||
this.items.fetch();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise which resolves with a map indicating
|
||||
* whether a given identity is provided by this entity.
|
||||
* @private
|
||||
* @method _converse.DiscoEntity#getIdentity
|
||||
* @param { String } category - The identity category
|
||||
* @param { String } type - The identity type
|
||||
*/
|
||||
async getIdentity (category, type) {
|
||||
await this.waitUntilFeaturesDiscovered;
|
||||
return this.identities.findWhere({
|
||||
'category': category,
|
||||
'type': type
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise which resolves with a map indicating
|
||||
* whether a given feature is supported.
|
||||
* @private
|
||||
* @method _converse.DiscoEntity#hasFeature
|
||||
* @param { String } feature - The feature that might be supported.
|
||||
*/
|
||||
async hasFeature (feature) {
|
||||
await this.waitUntilFeaturesDiscovered
|
||||
if (this.features.findWhere({'var': feature})) {
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
onFeatureAdded (feature) {
|
||||
feature.entity = this;
|
||||
/**
|
||||
* Triggered when Converse has learned of a service provided by the XMPP server.
|
||||
* See XEP-0030.
|
||||
* @event _converse#serviceDiscovered
|
||||
* @type { Model }
|
||||
* @example _converse.api.listen.on('featuresDiscovered', feature => { ... });
|
||||
*/
|
||||
api.trigger('serviceDiscovered', feature);
|
||||
},
|
||||
|
||||
onFieldAdded (field) {
|
||||
field.entity = this;
|
||||
/**
|
||||
* Triggered when Converse has learned of a disco extension field.
|
||||
* See XEP-0030.
|
||||
* @event _converse#discoExtensionFieldDiscovered
|
||||
* @example _converse.api.listen.on('discoExtensionFieldDiscovered', () => { ... });
|
||||
*/
|
||||
api.trigger('discoExtensionFieldDiscovered', field);
|
||||
},
|
||||
|
||||
async fetchFeatures (options) {
|
||||
if (options.ignore_cache) {
|
||||
this.queryInfo();
|
||||
} else {
|
||||
const store_id = this.features.browserStorage.name;
|
||||
const result = await this.features.browserStorage.store.getItem(store_id);
|
||||
if (result && result.length === 0 || result === null) {
|
||||
this.queryInfo();
|
||||
} else {
|
||||
this.features.fetch({
|
||||
add: true,
|
||||
success: () => {
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
this.trigger('featuresDiscovered');
|
||||
}
|
||||
});
|
||||
this.identities.fetch({add: true});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async queryInfo () {
|
||||
let stanza;
|
||||
try {
|
||||
stanza = await api.disco.info(this.get('jid'), null);
|
||||
} catch (iq) {
|
||||
iq === null ? log.error(`Timeout for disco#info query for ${this.get('jid')}`) : log.error(iq);
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
return;
|
||||
}
|
||||
this.onInfo(stanza);
|
||||
},
|
||||
|
||||
onDiscoItems (stanza) {
|
||||
sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza).forEach(item => {
|
||||
if (item.getAttribute("node")) {
|
||||
// XXX: Ignore nodes for now.
|
||||
// See: https://xmpp.org/extensions/xep-0030.html#items-nodes
|
||||
return;
|
||||
}
|
||||
const jid = item.getAttribute('jid');
|
||||
if (this.items.get(jid) === undefined) {
|
||||
const entity = _converse.disco_entities.get(jid);
|
||||
if (entity) {
|
||||
this.items.add(entity);
|
||||
} else {
|
||||
this.items.create({'jid': jid});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async queryForItems () {
|
||||
if (this.identities.where({'category': 'server'}).length === 0) {
|
||||
// Don't fetch features and items if this is not a
|
||||
// server or a conference component.
|
||||
return;
|
||||
}
|
||||
const stanza = await api.disco.items(this.get('jid'));
|
||||
this.onDiscoItems(stanza);
|
||||
},
|
||||
|
||||
onInfo (stanza) {
|
||||
Array.from(stanza.querySelectorAll('identity')).forEach(identity => {
|
||||
this.identities.create({
|
||||
'category': identity.getAttribute('category'),
|
||||
'type': identity.getAttribute('type'),
|
||||
'name': identity.getAttribute('name')
|
||||
});
|
||||
});
|
||||
|
||||
sizzle(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza).forEach(form => {
|
||||
const data = {};
|
||||
sizzle('field', form).forEach(field => {
|
||||
data[field.getAttribute('var')] = {
|
||||
'value': field.querySelector('value')?.textContent,
|
||||
'type': field.getAttribute('type')
|
||||
};
|
||||
});
|
||||
this.dataforms.create(data);
|
||||
});
|
||||
|
||||
if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
|
||||
this.queryForItems();
|
||||
}
|
||||
Array.from(stanza.querySelectorAll('feature')).forEach(feature => {
|
||||
this.features.create({
|
||||
'var': feature.getAttribute('var'),
|
||||
'from': stanza.getAttribute('from')
|
||||
});
|
||||
});
|
||||
|
||||
// XEP-0128 Service Discovery Extensions
|
||||
sizzle('x[type="result"][xmlns="jabber:x:data"] field', stanza).forEach(field => {
|
||||
this.fields.create({
|
||||
'var': field.getAttribute('var'),
|
||||
'value': field.querySelector('value')?.textContent,
|
||||
'from': stanza.getAttribute('from')
|
||||
});
|
||||
});
|
||||
|
||||
this.waitUntilFeaturesDiscovered.resolve(this);
|
||||
this.trigger('featuresDiscovered');
|
||||
}
|
||||
});
|
||||
|
||||
export default DiscoEntity;
|
60
src/headless/plugins/disco/index.js
Normal file
60
src/headless/plugins/disco/index.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Converse plugin which add support for XEP-0030: Service Discovery
|
||||
*/
|
||||
import DiscoEntities from './entities.js';
|
||||
import DiscoEntity from './entity.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core.js';
|
||||
import { initializeDisco, initStreamFeatures, notifyStreamFeaturesAdded, populateStreamFeatures } from './utils.js';
|
||||
import disco_api from './api.js';
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
|
||||
converse.plugins.add('converse-disco', {
|
||||
initialize () {
|
||||
Object.assign(api, disco_api);
|
||||
|
||||
api.promises.add('discoInitialized');
|
||||
api.promises.add('streamFeaturesAdded');
|
||||
|
||||
_converse.DiscoEntity = DiscoEntity;
|
||||
_converse.DiscoEntities = DiscoEntities;
|
||||
|
||||
_converse.disco = {
|
||||
_identities: [],
|
||||
_features: []
|
||||
};
|
||||
|
||||
api.listen.on('userSessionInitialized', async () => {
|
||||
initStreamFeatures();
|
||||
if (_converse.connfeedback.get('connection_status') === Strophe.Status.ATTACHED) {
|
||||
// When re-attaching to a BOSH session, we fetch the stream features from the cache.
|
||||
await new Promise((success, error) => _converse.stream_features.fetch({ success, error }));
|
||||
notifyStreamFeaturesAdded();
|
||||
}
|
||||
});
|
||||
api.listen.on('beforeResourceBinding', populateStreamFeatures);
|
||||
api.listen.on('reconnected', initializeDisco);
|
||||
api.listen.on('connected', initializeDisco);
|
||||
|
||||
api.listen.on('beforeTearDown', async () => {
|
||||
api.promises.add('streamFeaturesAdded');
|
||||
if (_converse.stream_features) {
|
||||
await _converse.stream_features.clearStore();
|
||||
delete _converse.stream_features;
|
||||
}
|
||||
});
|
||||
|
||||
api.listen.on('clearSession', () => {
|
||||
if (_converse.shouldClearCache() && _converse.disco_entities) {
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.features.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.identities.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.dataforms.clearStore());
|
||||
Array.from(_converse.disco_entities.models).forEach(e => e.fields.clearStore());
|
||||
_converse.disco_entities.clearStore();
|
||||
delete _converse.disco_entities;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
125
src/headless/plugins/disco/utils.js
Normal file
125
src/headless/plugins/disco/utils.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { _converse, api, converse } from "@converse/headless/core.js";
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
|
||||
const { Strophe, $iq } = converse.env;
|
||||
|
||||
|
||||
function onDiscoInfoRequest (identities, features, stanza) {
|
||||
const node = stanza.getElementsByTagName('query')[0].getAttribute('node');
|
||||
const attrs = {xmlns: Strophe.NS.DISCO_INFO};
|
||||
if (node) { attrs.node = node; }
|
||||
|
||||
const iqresult = $iq({'type': 'result', 'id': stanza.getAttribute('id')});
|
||||
const from = stanza.getAttribute('from');
|
||||
if (from !== null) {
|
||||
iqresult.attrs({'to': from});
|
||||
}
|
||||
iqresult.c('query', attrs);
|
||||
_converse.disco._identities.forEach(identity => {
|
||||
const attrs = {
|
||||
'category': identity.category,
|
||||
'type': identity.type
|
||||
};
|
||||
if (identity.name) {
|
||||
attrs.name = identity.name;
|
||||
}
|
||||
if (identity.lang) {
|
||||
attrs['xml:lang'] = identity.lang;
|
||||
}
|
||||
iqresult.c('identity', attrs).up();
|
||||
});
|
||||
_converse.disco._features.forEach(feature => iqresult.c('feature', {'var': feature}).up());
|
||||
api.send(iqresult.tree());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function addClientFeatures () {
|
||||
// See https://xmpp.org/registrar/disco-categories.html
|
||||
api.disco.own.identities.add('client', 'web', 'Converse');
|
||||
|
||||
api.disco.own.features.add(Strophe.NS.CHATSTATES);
|
||||
api.disco.own.features.add(Strophe.NS.DISCO_INFO);
|
||||
api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support
|
||||
if (api.settings.get("message_carbons")) {
|
||||
api.disco.own.features.add(Strophe.NS.CARBONS);
|
||||
}
|
||||
/**
|
||||
* Triggered in converse-disco once the core disco features of
|
||||
* Converse have been added.
|
||||
* @event _converse#addClientFeatures
|
||||
* @example _converse.api.listen.on('addClientFeatures', () => { ... });
|
||||
*/
|
||||
api.trigger('addClientFeatures');
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
export async function initializeDisco () {
|
||||
addClientFeatures();
|
||||
_converse.connection.addHandler(
|
||||
stanza => onDiscoInfoRequest(stanza),
|
||||
Strophe.NS.DISCO_INFO,
|
||||
'iq', 'get', null, null
|
||||
);
|
||||
|
||||
_converse.disco_entities = new _converse.DiscoEntities();
|
||||
const id = `converse.disco-entities-${_converse.bare_jid}`;
|
||||
_converse.disco_entities.browserStorage = _converse.createStore(id, 'session');
|
||||
const collection = await _converse.disco_entities.fetchEntities();
|
||||
if (collection.length === 0 || !collection.get(_converse.domain)) {
|
||||
// If we don't have an entity for our own XMPP server,
|
||||
// create one.
|
||||
_converse.disco_entities.create({'jid': _converse.domain});
|
||||
}
|
||||
/**
|
||||
* Triggered once the `converse-disco` plugin has been initialized and the
|
||||
* `_converse.disco_entities` collection will be available and populated with at
|
||||
* least the service discovery features of the user's own server.
|
||||
* @event _converse#discoInitialized
|
||||
* @example _converse.api.listen.on('discoInitialized', () => { ... });
|
||||
*/
|
||||
api.trigger('discoInitialized');
|
||||
}
|
||||
|
||||
export function initStreamFeatures () {
|
||||
// Initialize the stream_features collection, and if we're
|
||||
// re-attaching to a pre-existing BOSH session, we restore the
|
||||
// features from cache.
|
||||
// Otherwise the features will be created once we've received them
|
||||
// from the server (see populateStreamFeatures).
|
||||
if (!_converse.stream_features) {
|
||||
const bare_jid = Strophe.getBareJidFromJid(_converse.jid);
|
||||
const id = `converse.stream-features-${bare_jid}`;
|
||||
api.promises.add('streamFeaturesAdded');
|
||||
_converse.stream_features = new Collection();
|
||||
_converse.stream_features.browserStorage = _converse.createStore(id, "session");
|
||||
}
|
||||
}
|
||||
|
||||
export function notifyStreamFeaturesAdded () {
|
||||
/**
|
||||
* Triggered as soon as the stream features are known.
|
||||
* If you want to check whether a stream feature is supported before proceeding,
|
||||
* then you'll first want to wait for this event.
|
||||
* @event _converse#streamFeaturesAdded
|
||||
* @example _converse.api.listen.on('streamFeaturesAdded', () => { ... });
|
||||
*/
|
||||
api.trigger('streamFeaturesAdded');
|
||||
}
|
||||
|
||||
export function populateStreamFeatures () {
|
||||
// Strophe.js sets the <stream:features> element on the
|
||||
// Strophe.Connection instance (_converse.connection).
|
||||
//
|
||||
// Once this is done, we populate the _converse.stream_features collection
|
||||
// and trigger streamFeaturesAdded.
|
||||
initStreamFeatures();
|
||||
Array.from(_converse.connection.features.childNodes).forEach(feature => {
|
||||
_converse.stream_features.create({
|
||||
'name': feature.nodeName,
|
||||
'xmlns': feature.getAttribute('xmlns')
|
||||
});
|
||||
});
|
||||
notifyStreamFeaturesAdded();
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import mam_api from './api.js';
|
||||
import '../disco';
|
||||
import '../disco/index.js';
|
||||
import {
|
||||
onMAMError,
|
||||
onMAMPreferences,
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import difference from 'lodash-es/difference';
|
||||
import indexOf from 'lodash-es/indexOf';
|
||||
import log from "@converse/headless/log";
|
||||
import { api, converse } from '@converse/headless/core.js';
|
||||
import { difference, indexOf } from 'lodash-es';
|
||||
import { parseMemberListIQ } from '../parsers.js';
|
||||
|
||||
const { Strophe, $iq, u } = converse.env;
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
/**
|
||||
* @module converse-muc
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Implements the non-view logic for XEP-0045 Multi-User Chat
|
||||
*/
|
||||
import '../chat/index.js';
|
||||
import '../disco';
|
||||
import '../disco/index.js';
|
||||
import '../emoji/index.js';
|
||||
import ChatRoomMessageMixin from './message.js';
|
||||
import ChatRoomMixin from './muc.js';
|
||||
import ChatRoomOccupant from './occupant.js';
|
||||
import ChatRoomOccupants from './occupants.js';
|
||||
import log from '../../log';
|
||||
import muc_api from './api.js';
|
||||
import affiliations_api from './affiliations/api.js';
|
||||
import { computeAffiliationsDelta } from './affiliations/utils.js';
|
||||
import isObject from 'lodash-es/isObject';
|
||||
import log from '@converse/headless/log';
|
||||
import muc_api from './api.js';
|
||||
import u from '../../utils/form';
|
||||
import { Collection } from '@converse/skeletor/src/collection';
|
||||
import { _converse, api, converse } from '../../core.js';
|
||||
import { isObject } from 'lodash-es';
|
||||
import { computeAffiliationsDelta } from './affiliations/utils.js';
|
||||
|
||||
export const ROLES = ['moderator', 'participant', 'visitor'];
|
||||
export const AFFILIATIONS = ['owner', 'admin', 'member', 'outcast', 'none'];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import "./disco";
|
||||
import "./disco/index.js";
|
||||
import { _converse, api, converse } from "../core.js";
|
||||
import log from "../log.js";
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import { isNaN, isObject } from "lodash-es";
|
||||
import isNaN from "lodash-es/isNaN";
|
||||
import isObject from "lodash-es/isObject";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
* @license Mozilla Public License (MPLv2)
|
||||
* @description This is the core utilities module.
|
||||
*/
|
||||
import { Strophe } from 'strophe.js/src/strophe';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { compact, last, isElement, isObject } from "lodash-es";
|
||||
import compact from "lodash-es/compact";
|
||||
import isElement from "lodash-es/isElement";
|
||||
import isObject from "lodash-es/isObject";
|
||||
import last from "lodash-es/last";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from "sizzle";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { Strophe } from 'strophe.js/src/strophe';
|
||||
|
||||
/**
|
||||
* The utils object
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'shared/autocomplete/index.js';
|
||||
import BootstrapModal from "./base.js";
|
||||
import compact from 'lodash-es/compact';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import tpl_add_contact_modal from "./templates/add-contact.js";
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { compact, debounce } from "lodash-es";
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import invokeMap from 'lodash-es/invokeMap';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { invokeMap } from 'lodash-es';
|
||||
|
||||
const { u } = converse.env;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import debounce from 'lodash-es/debounce';
|
||||
import { _converse } from '@converse/headless/core';
|
||||
import { applyDragResistance } from './utils.js';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
const DragResizableMixin = {
|
||||
initDragResize () {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import './components/minimized-chat.js';
|
||||
import 'plugins/chatview/index.js';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import MinimizedChats from './view.js';
|
||||
import MinimizedChatsToggle from './toggle.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
|
@ -16,7 +17,6 @@ import {
|
|||
onMinimizedChanged,
|
||||
trimChats
|
||||
} from './utils.js';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import './styles/minimize.scss';
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import BootstrapModal from "modals/base.js";
|
||||
import head from "lodash-es/head";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_list_chatrooms_modal from "../templates/muc-list.js";
|
||||
import tpl_muc_description from "../templates/muc-description.js";
|
||||
|
@ -6,7 +7,6 @@ import tpl_spinner from "templates/spinner.js";
|
|||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { getAttributes } from '@converse/headless/shared/parsers';
|
||||
import { head } from "lodash-es";
|
||||
|
||||
const { Strophe, $iq, sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import log from "@converse/headless/log";
|
||||
import pick from "lodash-es/pick";
|
||||
import tpl_form_input from "templates/form_input.js";
|
||||
import tpl_form_url from "templates/form_url.js";
|
||||
import tpl_form_username from "templates/form_username.js";
|
||||
|
@ -8,7 +9,6 @@ import utils from "@converse/headless/utils/form";
|
|||
import { ElementView } from "@converse/skeletor/src/element";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { pick } from "lodash-es";
|
||||
import { render } from 'lit-html';
|
||||
|
||||
// Strophe methods for building stanzas
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import debounce from "lodash-es/debounce";
|
||||
import tpl_roster_filter from "./templates/roster_filter.js";
|
||||
import { ElementView } from '@converse/skeletor/src/element.js';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
import { debounce } from "lodash-es";
|
||||
import { render } from 'lit-html';
|
||||
|
||||
export const RosterFilter = Model.extend({
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* @description This is the DOM/HTML utilities module.
|
||||
*/
|
||||
import URI from "urijs";
|
||||
import isFunction from "lodash-es/isFunction";
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_audio from "../templates/audio.js";
|
||||
import tpl_file from "../templates/file.js";
|
||||
|
@ -20,7 +21,6 @@ import tpl_video from "../templates/video.js";
|
|||
import u from "../headless/utils/core";
|
||||
import { api, converse } from "@converse/headless/core";
|
||||
import { html, render } from "lit-html";
|
||||
import { isFunction } from "lodash-es";
|
||||
|
||||
const { sizzle } = converse.env;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user