converse-disco.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. // Converse.js
  2. // http://conversejs.org
  3. //
  4. // Copyright (c) 2013-2018, the Converse developers
  5. // Licensed under the Mozilla Public License (MPLv2)
  6. /* This is a Converse plugin which add support for XEP-0030: Service Discovery */
  7. (function (root, factory) {
  8. define(["./converse-core", "sizzle"], factory);
  9. }(this, function (converse, sizzle) {
  10. const { Backbone, Promise, Strophe, $iq, b64_sha1, utils, _, f } = converse.env;
  11. converse.plugins.add('converse-disco', {
  12. initialize () {
  13. /* The initialize function gets called as soon as the plugin is
  14. * loaded by converse.js's plugin machinery.
  15. */
  16. const { _converse } = this;
  17. // Promises exposed by this plugin
  18. _converse.api.promises.add('discoInitialized');
  19. _converse.DiscoEntity = Backbone.Model.extend({
  20. /* A Disco Entity is a JID addressable entity that can be queried
  21. * for features.
  22. *
  23. * See XEP-0030: https://xmpp.org/extensions/xep-0030.html
  24. */
  25. idAttribute: 'jid',
  26. initialize () {
  27. this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
  28. this.dataforms = new Backbone.Collection();
  29. this.dataforms.browserStorage = new Backbone.BrowserStorage.session(
  30. b64_sha1(`converse.dataforms-{this.get('jid')}`)
  31. );
  32. this.features = new Backbone.Collection();
  33. this.features.browserStorage = new Backbone.BrowserStorage.session(
  34. b64_sha1(`converse.features-${this.get('jid')}`)
  35. );
  36. this.features.on('add', this.onFeatureAdded, this);
  37. this.fields = new Backbone.Collection();
  38. this.fields.browserStorage = new Backbone.BrowserStorage.session(
  39. b64_sha1(`converse.fields-${this.get('jid')}`)
  40. );
  41. this.fields.on('add', this.onFieldAdded, this);
  42. this.identities = new Backbone.Collection();
  43. this.identities.browserStorage = new Backbone.BrowserStorage.session(
  44. b64_sha1(`converse.identities-${this.get('jid')}`)
  45. );
  46. this.fetchFeatures();
  47. this.items = new _converse.DiscoEntities();
  48. this.items.browserStorage = new Backbone.BrowserStorage.session(
  49. b64_sha1(`converse.disco-items-${this.get('jid')}`)
  50. );
  51. this.items.fetch();
  52. },
  53. getIdentity (category, type) {
  54. /* Returns a Promise which resolves with a map indicating
  55. * whether a given identity is provided.
  56. *
  57. * Parameters:
  58. * (String) category - The identity category
  59. * (String) type - The identity type
  60. */
  61. const entity = this;
  62. return new Promise((resolve, reject) => {
  63. function fulfillPromise () {
  64. const model = entity.identities.findWhere({
  65. 'category': category,
  66. 'type': type
  67. });
  68. resolve(model);
  69. }
  70. entity.waitUntilFeaturesDiscovered
  71. .then(fulfillPromise)
  72. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  73. });
  74. },
  75. hasFeature (feature) {
  76. /* Returns a Promise which resolves with a map indicating
  77. * whether a given feature is supported.
  78. *
  79. * Parameters:
  80. * (String) feature - The feature that might be supported.
  81. */
  82. const entity = this;
  83. return new Promise((resolve, reject) => {
  84. function fulfillPromise () {
  85. if (entity.features.findWhere({'var': feature})) {
  86. resolve(entity);
  87. } else {
  88. resolve();
  89. }
  90. }
  91. entity.waitUntilFeaturesDiscovered
  92. .then(fulfillPromise)
  93. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  94. });
  95. },
  96. onFeatureAdded (feature) {
  97. feature.entity = this;
  98. _converse.emit('serviceDiscovered', feature);
  99. },
  100. onFieldAdded (field) {
  101. field.entity = this;
  102. _converse.emit('discoExtensionFieldDiscovered', field);
  103. },
  104. fetchFeatures () {
  105. if (this.features.browserStorage.records.length === 0) {
  106. this.queryInfo();
  107. } else {
  108. this.features.fetch({
  109. add: true,
  110. success: () => {
  111. this.waitUntilFeaturesDiscovered.resolve(this);
  112. this.trigger('featuresDiscovered');
  113. }
  114. });
  115. this.identities.fetch({add: true});
  116. }
  117. },
  118. queryInfo () {
  119. _converse.api.disco.info(this.get('jid'), null)
  120. .then(stanza => this.onInfo(stanza))
  121. .catch(iq => {
  122. this.waitUntilFeaturesDiscovered.resolve(this);
  123. _converse.log(iq, Strophe.LogLevel.ERROR);
  124. });
  125. },
  126. onDiscoItems (stanza) {
  127. _.each(sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza), (item) => {
  128. if (item.getAttribute("node")) {
  129. // XXX: ignore nodes for now.
  130. // See: https://xmpp.org/extensions/xep-0030.html#items-nodes
  131. return;
  132. }
  133. const jid = item.getAttribute('jid');
  134. if (_.isUndefined(this.items.get(jid))) {
  135. const entity = _converse.disco_entities.get(jid);
  136. if (entity) {
  137. this.items.add(entity);
  138. } else {
  139. this.items.create({'jid': jid});
  140. }
  141. }
  142. });
  143. },
  144. queryForItems () {
  145. if (_.isEmpty(this.identities.where({'category': 'server'}))) {
  146. // Don't fetch features and items if this is not a
  147. // server or a conference component.
  148. return;
  149. }
  150. _converse.api.disco.items(this.get('jid')).then(stanza => this.onDiscoItems(stanza));
  151. },
  152. onInfo (stanza) {
  153. _.forEach(stanza.querySelectorAll('identity'), (identity) => {
  154. this.identities.create({
  155. 'category': identity.getAttribute('category'),
  156. 'type': identity.getAttribute('type'),
  157. 'name': identity.getAttribute('name')
  158. });
  159. });
  160. _.each(sizzle(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza), (form) => {
  161. const data = {};
  162. _.each(form.querySelectorAll('field'), (field) => {
  163. data[field.getAttribute('var')] = {
  164. 'value': _.get(field.querySelector('value'), 'textContent'),
  165. 'type': field.getAttribute('type')
  166. };
  167. });
  168. this.dataforms.create(data);
  169. });
  170. if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) {
  171. this.queryForItems();
  172. }
  173. _.forEach(stanza.querySelectorAll('feature'), feature => {
  174. this.features.create({
  175. 'var': feature.getAttribute('var'),
  176. 'from': stanza.getAttribute('from')
  177. });
  178. });
  179. // XEP-0128 Service Discovery Extensions
  180. _.forEach(sizzle('x[type="result"][xmlns="jabber:x:data"] field', stanza), field => {
  181. this.fields.create({
  182. 'var': field.getAttribute('var'),
  183. 'value': _.get(field.querySelector('value'), 'textContent'),
  184. 'from': stanza.getAttribute('from')
  185. });
  186. });
  187. this.waitUntilFeaturesDiscovered.resolve(this);
  188. this.trigger('featuresDiscovered');
  189. }
  190. });
  191. _converse.DiscoEntities = Backbone.Collection.extend({
  192. model: _converse.DiscoEntity,
  193. fetchEntities () {
  194. return new Promise((resolve, reject) => {
  195. this.fetch({
  196. add: true,
  197. success: resolve,
  198. error () {
  199. reject (new Error("Could not fetch disco entities"));
  200. }
  201. });
  202. });
  203. }
  204. });
  205. function addClientFeatures () {
  206. // See http://xmpp.org/registrar/disco-categories.html
  207. _converse.api.disco.own.identities.add('client', 'web', 'Converse');
  208. _converse.api.disco.own.features.add(Strophe.NS.BOSH);
  209. _converse.api.disco.own.features.add(Strophe.NS.CHATSTATES);
  210. _converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO);
  211. _converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support
  212. if (_converse.message_carbons) {
  213. _converse.api.disco.own.features.add(Strophe.NS.CARBONS);
  214. }
  215. _converse.emit('addClientFeatures');
  216. return this;
  217. }
  218. function initStreamFeatures () {
  219. _converse.stream_features = new Backbone.Collection();
  220. _converse.stream_features.browserStorage = new Backbone.BrowserStorage.session(
  221. b64_sha1(`converse.stream-features-${_converse.bare_jid}`)
  222. );
  223. _converse.stream_features.fetch({
  224. success (collection) {
  225. if (collection.length === 0 && _converse.connection.features) {
  226. _.forEach(
  227. _converse.connection.features.childNodes,
  228. (feature) => {
  229. _converse.stream_features.create({
  230. 'name': feature.nodeName,
  231. 'xmlns': feature.getAttribute('xmlns')
  232. });
  233. });
  234. }
  235. }
  236. });
  237. _converse.emit('streamFeaturesAdded');
  238. }
  239. function initializeDisco () {
  240. addClientFeatures();
  241. _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
  242. _converse.disco_entities = new _converse.DiscoEntities();
  243. _converse.disco_entities.browserStorage = new Backbone.BrowserStorage.session(
  244. b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)
  245. );
  246. _converse.disco_entities.fetchEntities().then((collection) => {
  247. if (collection.length === 0 || !collection.get(_converse.domain)) {
  248. // If we don't have an entity for our own XMPP server,
  249. // create one.
  250. _converse.disco_entities.create({'jid': _converse.domain});
  251. }
  252. _converse.emit('discoInitialized');
  253. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  254. }
  255. _converse.api.listen.on('sessionInitialized', initStreamFeatures);
  256. _converse.api.listen.on('reconnected', initializeDisco);
  257. _converse.api.listen.on('connected', initializeDisco);
  258. _converse.api.listen.on('beforeTearDown', () => {
  259. if (_converse.disco_entities) {
  260. _converse.disco_entities.each((entity) => {
  261. entity.features.reset();
  262. entity.features.browserStorage._clear();
  263. });
  264. _converse.disco_entities.reset();
  265. _converse.disco_entities.browserStorage._clear();
  266. }
  267. });
  268. const plugin = this;
  269. plugin._identities = [];
  270. plugin._features = [];
  271. function onDiscoInfoRequest (stanza) {
  272. const node = stanza.getElementsByTagName('query')[0].getAttribute('node');
  273. const attrs = {xmlns: Strophe.NS.DISCO_INFO};
  274. if (node) { attrs.node = node; }
  275. const iqresult = $iq({'type': 'result', 'id': stanza.getAttribute('id')});
  276. const from = stanza.getAttribute('from');
  277. if (from !== null) {
  278. iqresult.attrs({'to': from});
  279. }
  280. iqresult.c('query', attrs);
  281. _.each(plugin._identities, (identity) => {
  282. const attrs = {
  283. 'category': identity.category,
  284. 'type': identity.type
  285. };
  286. if (identity.name) {
  287. attrs.name = identity.name;
  288. }
  289. if (identity.lang) {
  290. attrs['xml:lang'] = identity.lang;
  291. }
  292. iqresult.c('identity', attrs).up();
  293. });
  294. _.each(plugin._features, (feature) => {
  295. iqresult.c('feature', {'var': feature}).up();
  296. });
  297. _converse.connection.send(iqresult.tree());
  298. return true;
  299. }
  300. _.extend(_converse.api, {
  301. /**
  302. * The XEP-0030 service discovery API
  303. *
  304. * This API lets you discover information about entities on the
  305. * XMPP network.
  306. *
  307. * @namespace _converse.api.disco
  308. * @memberOf _converse.api
  309. */
  310. 'disco': {
  311. /**
  312. * @namespace _converse.api.disco.stream
  313. * @memberOf _converse.api.disco
  314. */
  315. 'stream': {
  316. /**
  317. * @method _converse.api.disco.stream.getFeature
  318. * @param {String} name The feature name
  319. * @param {String} xmlns The XML namespace
  320. * @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
  321. */
  322. 'getFeature': function (name, xmlns) {
  323. if (_.isNil(name) || _.isNil(xmlns)) {
  324. throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
  325. }
  326. return _converse.stream_features.findWhere({'name': name, 'xmlns': xmlns});
  327. }
  328. },
  329. /**
  330. * @namespace _converse.api.disco.own
  331. * @memberOf _converse.api.disco
  332. */
  333. 'own': {
  334. /**
  335. * @namespace _converse.api.disco.own.identities
  336. * @memberOf _converse.api.disco.own
  337. */
  338. 'identities': {
  339. /**
  340. * Lets you add new identities for this client (i.e. instance of Converse)
  341. * @method _converse.api.disco.own.identities.add
  342. *
  343. * @param {String} category - server, client, gateway, directory, etc.
  344. * @param {String} type - phone, pc, web, etc.
  345. * @param {String} name - "Converse"
  346. * @param {String} lang - en, el, de, etc.
  347. *
  348. * @example _converse.api.disco.own.identities.clear();
  349. */
  350. add (category, type, name, lang) {
  351. for (var i=0; i<plugin._identities.length; i++) {
  352. if (plugin._identities[i].category == category &&
  353. plugin._identities[i].type == type &&
  354. plugin._identities[i].name == name &&
  355. plugin._identities[i].lang == lang) {
  356. return false;
  357. }
  358. }
  359. plugin._identities.push({category: category, type: type, name: name, lang: lang});
  360. },
  361. /**
  362. * Clears all previously registered identities.
  363. * @method _converse.api.disco.own.identities.clear
  364. * @example _converse.api.disco.own.identities.clear();
  365. */
  366. clear () {
  367. plugin._identities = []
  368. },
  369. /**
  370. * Returns all of the identities registered for this client
  371. * (i.e. instance of Converse).
  372. * @method _converse.api.disco.identities.get
  373. * @example const identities = _converse.api.disco.own.identities.get();
  374. */
  375. get () {
  376. return plugin._identities;
  377. }
  378. },
  379. /**
  380. * @namespace _converse.api.disco.own.features
  381. * @memberOf _converse.api.disco.own
  382. */
  383. 'features': {
  384. /**
  385. * Lets you register new disco features for this client (i.e. instance of Converse)
  386. * @method _converse.api.disco.own.features.add
  387. * @param {String} name - e.g. http://jabber.org/protocol/caps
  388. * @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
  389. */
  390. add (name) {
  391. for (var i=0; i<plugin._features.length; i++) {
  392. if (plugin._features[i] == name) { return false; }
  393. }
  394. plugin._features.push(name);
  395. },
  396. /**
  397. * Clears all previously registered features.
  398. * @method _converse.api.disco.own.features.clear
  399. * @example _converse.api.disco.own.features.clear();
  400. */
  401. clear () {
  402. plugin._features = []
  403. },
  404. /**
  405. * Returns all of the features registered for this client (i.e. instance of Converse).
  406. * @method _converse.api.disco.own.features.get
  407. * @example const features = _converse.api.disco.own.features.get();
  408. */
  409. get () {
  410. return plugin._features;
  411. }
  412. }
  413. },
  414. /**
  415. * Query for information about an XMPP entity
  416. *
  417. * @method _converse.api.disco.info
  418. * @param {string} jid The Jabber ID of the entity to query
  419. * @param {string} [node] A specific node identifier associated with the JID
  420. * @returns {promise} Promise which resolves once we have a result from the server.
  421. */
  422. 'info' (jid, node) {
  423. const attrs = {xmlns: Strophe.NS.DISCO_INFO};
  424. if (node) {
  425. attrs.node = node;
  426. }
  427. const info = $iq({
  428. 'from': _converse.connection.jid,
  429. 'to':jid,
  430. 'type':'get'
  431. }).c('query', attrs);
  432. return _converse.api.sendIQ(info);
  433. },
  434. /**
  435. * Query for items associated with an XMPP entity
  436. *
  437. * @method _converse.api.disco.items
  438. * @param {string} jid The Jabber ID of the entity to query for items
  439. * @param {string} [node] A specific node identifier associated with the JID
  440. * @returns {promise} Promise which resolves once we have a result from the server.
  441. */
  442. 'items' (jid, node) {
  443. const attrs = {'xmlns': Strophe.NS.DISCO_ITEMS};
  444. if (node) {
  445. attrs.node = node;
  446. }
  447. return _converse.api.sendIQ(
  448. $iq({
  449. 'from': _converse.connection.jid,
  450. 'to':jid,
  451. 'type':'get'
  452. }).c('query', attrs)
  453. );
  454. },
  455. /**
  456. * Namespace for methods associated with disco entities
  457. *
  458. * @namespace _converse.api.disco.entities
  459. * @memberOf _converse.api.disco
  460. */
  461. 'entities': {
  462. /**
  463. * Get the the corresponding `DiscoEntity` instance.
  464. *
  465. * @method _converse.api.disco.entities.get
  466. * @param {string} jid The Jabber ID of the entity
  467. * @param {boolean} [create] Whether the entity should be created if it doesn't exist.
  468. * @example _converse.api.disco.entities.get(jid);
  469. */
  470. 'get' (jid, create=false) {
  471. return _converse.api.waitUntil('discoInitialized')
  472. .then(() => {
  473. if (_.isNil(jid)) {
  474. return _converse.disco_entities;
  475. }
  476. const entity = _converse.disco_entities.get(jid);
  477. if (entity || !create) {
  478. return entity;
  479. }
  480. return _converse.disco_entities.create({'jid': jid});
  481. });
  482. }
  483. },
  484. /**
  485. * Used to determine whether an entity supports a given feature.
  486. *
  487. * @method _converse.api.disco.supports
  488. * @param {string} feature The feature that might be
  489. * supported. In the XML stanza, this is the `var`
  490. * attribute of the `<feature>` element. For
  491. * example: `http://jabber.org/protocol/muc`
  492. * @param {string} jid The JID of the entity
  493. * (and its associated items) which should be queried
  494. * @returns {promise} A promise which resolves with a list containing
  495. * _converse.Entity instances representing the entity
  496. * itself or those items associated with the entity if
  497. * they support the given feature.
  498. *
  499. * @example
  500. * _converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid)
  501. * .then(value => {
  502. * // `value` is a map with two keys, `supported` and `feature`.
  503. * if (value.supported) {
  504. * // The feature is supported
  505. * } else {
  506. * // The feature is not supported
  507. * }
  508. * }).catch(() => {
  509. * _converse.log(
  510. * "Error or timeout while checking for feature support",
  511. * Strophe.LogLevel.ERROR
  512. * );
  513. * });
  514. */
  515. 'supports' (feature, jid) {
  516. if (_.isNil(jid)) {
  517. throw new TypeError('api.disco.supports: You need to provide an entity JID');
  518. }
  519. return _converse.api.waitUntil('discoInitialized')
  520. .then(() => _converse.api.disco.entities.get(jid, true))
  521. .then(entity => entity.waitUntilFeaturesDiscovered)
  522. .then(entity => {
  523. const promises = _.concat(
  524. entity.items.map(item => item.hasFeature(feature)),
  525. entity.hasFeature(feature)
  526. );
  527. return Promise.all(promises);
  528. }).then(result => f.filter(f.isObject, result));
  529. },
  530. /**
  531. * Refresh the features (and fields and identities) associated with a
  532. * disco entity by refetching them from the server
  533. *
  534. * @method _converse.api.disco.refreshFeatures
  535. * @param {string} jid The JID of the entity whose features are refreshed.
  536. * @returns {promise} A promise which resolves once the features have been refreshed
  537. * @example
  538. * await _converse.api.disco.refreshFeatures('room@conference.example.org');
  539. */
  540. 'refreshFeatures' (jid) {
  541. if (_.isNil(jid)) {
  542. throw new TypeError('api.disco.refreshFeatures: You need to provide an entity JID');
  543. }
  544. return _converse.api.waitUntil('discoInitialized')
  545. .then(() => _converse.api.disco.entities.get(jid, true))
  546. .then(entity => {
  547. entity.features.reset();
  548. entity.fields.reset();
  549. entity.identities.reset();
  550. entity.waitUntilFeaturesDiscovered = utils.getResolveablePromise()
  551. entity.queryInfo();
  552. return entity.waitUntilFeaturesDiscovered;
  553. })
  554. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  555. },
  556. /**
  557. * Return all the features associated with a disco entity
  558. *
  559. * @method _converse.api.disco.getFeatures
  560. * @param {string} jid The JID of the entity whose features are returned.
  561. * @returns {promise} A promise which resolves with the returned features
  562. * @example
  563. * const features = await _converse.api.disco.getFeatures('room@conference.example.org');
  564. */
  565. 'getFeatures' (jid) {
  566. if (_.isNil(jid)) {
  567. throw new TypeError('api.disco.getFeatures: You need to provide an entity JID');
  568. }
  569. return _converse.api.waitUntil('discoInitialized')
  570. .then(() => _converse.api.disco.entities.get(jid, true))
  571. .then(entity => entity.waitUntilFeaturesDiscovered)
  572. .then(entity => entity.features)
  573. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  574. },
  575. /**
  576. * Return all the service discovery extensions fields
  577. * associated with an entity.
  578. *
  579. * See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html)
  580. *
  581. * @method _converse.api.disco.getFields
  582. * @param {string} jid The JID of the entity whose fields are returned.
  583. * @example
  584. * const fields = await _converse.api.disco.getFields('room@conference.example.org');
  585. */
  586. 'getFields' (jid) {
  587. if (_.isNil(jid)) {
  588. throw new TypeError('api.disco.getFields: You need to provide an entity JID');
  589. }
  590. return _converse.api.waitUntil('discoInitialized')
  591. .then(() => _converse.api.disco.entities.get(jid, true))
  592. .then(entity => entity.waitUntilFeaturesDiscovered)
  593. .then(entity => entity.fields)
  594. .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  595. },
  596. /**
  597. * Get the identity (with the given category and type) for a given disco entity.
  598. *
  599. * For example, when determining support for PEP (personal eventing protocol), you
  600. * want to know whether the user's own JID has an identity with
  601. * `category='pubsub'` and `type='pep'` as explained in this section of
  602. * XEP-0163: https://xmpp.org/extensions/xep-0163.html#support
  603. *
  604. * @method _converse.api.disco.getIdentity
  605. * @param {string} The identity category.
  606. * In the XML stanza, this is the `category`
  607. * attribute of the `<identity>` element.
  608. * For example: 'pubsub'
  609. * @param {string} type The identity type.
  610. * In the XML stanza, this is the `type`
  611. * attribute of the `<identity>` element.
  612. * For example: 'pep'
  613. * @param {string} jid The JID of the entity which might have the identity
  614. * @returns {promise} A promise which resolves with a map indicating
  615. * whether an identity with a given type is provided by the entity.
  616. * @example
  617. * _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then(
  618. * function (identity) {
  619. * if (_.isNil(identity)) {
  620. * // The entity DOES NOT have this identity
  621. * } else {
  622. * // The entity DOES have this identity
  623. * }
  624. * }
  625. * ).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  626. */
  627. 'getIdentity' (category, type, jid) {
  628. return _converse.api.disco.entities.get(jid, true).then(e => e.getIdentity(category, type));
  629. }
  630. }
  631. });
  632. }
  633. });
  634. }));