converse-vcard.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Converse.js
  2. // http://conversejs.org
  3. //
  4. // Copyright (c) 2013-2018, the Converse.js developers
  5. // Licensed under the Mozilla Public License (MPLv2)
  6. (function (root, factory) {
  7. define(["./converse-core", "./templates/vcard.html"], factory);
  8. }(this, function (converse, tpl_vcard) {
  9. "use strict";
  10. const { Backbone, Promise, Strophe, _, $iq, $build, b64_sha1, moment, sizzle } = converse.env;
  11. const u = converse.env.utils;
  12. converse.plugins.add('converse-vcard', {
  13. initialize () {
  14. /* The initialize function gets called as soon as the plugin is
  15. * loaded by converse.js's plugin machinery.
  16. */
  17. const { _converse } = this;
  18. _converse.VCard = Backbone.Model.extend({
  19. defaults: {
  20. 'image': _converse.DEFAULT_IMAGE,
  21. 'image_type': _converse.DEFAULT_IMAGE_TYPE
  22. },
  23. set (key, val, options) {
  24. // Override Backbone.Model.prototype.set to make sure that the
  25. // default `image` and `image_type` values are maintained.
  26. let attrs;
  27. if (typeof key === 'object') {
  28. attrs = key;
  29. options = val;
  30. } else {
  31. (attrs = {})[key] = val;
  32. }
  33. if (_.has(attrs, 'image') && !attrs['image']) {
  34. attrs['image'] = _converse.DEFAULT_IMAGE;
  35. attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE;
  36. return Backbone.Model.prototype.set.call(this, attrs, options);
  37. } else {
  38. return Backbone.Model.prototype.set.apply(this, arguments);
  39. }
  40. }
  41. });
  42. _converse.VCards = Backbone.Collection.extend({
  43. model: _converse.VCard,
  44. initialize () {
  45. this.on('add', (vcard) => _converse.api.vcard.update(vcard));
  46. }
  47. });
  48. function onVCardData (jid, iq, callback) {
  49. const vcard = iq.querySelector('vCard');
  50. let result = {};
  51. if (!_.isNull(vcard)) {
  52. result = {
  53. 'stanza': iq,
  54. 'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
  55. 'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'),
  56. 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
  57. 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
  58. 'url': _.get(vcard.querySelector('URL'), 'textContent'),
  59. 'role': _.get(vcard.querySelector('ROLE'), 'textContent'),
  60. 'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent'),
  61. 'vcard_updated': moment().format(),
  62. 'vcard_error': undefined
  63. };
  64. }
  65. if (result.image) {
  66. const buffer = u.base64ToArrayBuffer(result['image']);
  67. crypto.subtle.digest('SHA-1', buffer)
  68. .then(ab => {
  69. result['image_hash'] = u.arrayBufferToHex(ab);
  70. if (callback) callback(result);
  71. });
  72. } else {
  73. if (callback) callback(result);
  74. }
  75. }
  76. function onVCardError (jid, iq, errback) {
  77. if (errback) {
  78. errback({
  79. 'stanza': iq,
  80. 'jid': jid,
  81. 'vcard_error': moment().format()
  82. });
  83. }
  84. }
  85. function createStanza (type, jid, vcard_el) {
  86. const iq = $iq(jid ? {'type': type, 'to': jid} : {'type': type});
  87. if (!vcard_el) {
  88. iq.c("vCard", {'xmlns': Strophe.NS.VCARD});
  89. } else {
  90. iq.cnode(vcard_el);
  91. }
  92. return iq;
  93. }
  94. function setVCard (jid, data) {
  95. if (!jid) {
  96. throw Error("No jid provided for the VCard data");
  97. }
  98. const vcard_el = Strophe.xmlHtmlNode(tpl_vcard(data)).firstElementChild;
  99. return _converse.api.sendIQ(createStanza("set", jid, vcard_el));
  100. }
  101. function getVCard (_converse, jid) {
  102. /* Request the VCard of another user. Returns a promise.
  103. *
  104. * Parameters:
  105. * (String) jid - The Jabber ID of the user whose VCard
  106. * is being requested.
  107. */
  108. const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
  109. return new Promise((resolve, reject) => {
  110. _converse.connection.sendIQ(
  111. createStanza("get", to),
  112. _.partial(onVCardData, jid, _, resolve),
  113. _.partial(onVCardError, jid, _, resolve),
  114. _converse.IQ_TIMEOUT
  115. );
  116. });
  117. }
  118. /* Event handlers */
  119. _converse.initVCardCollection = function () {
  120. _converse.vcards = new _converse.VCards();
  121. const id = b64_sha1(`converse.vcards`);
  122. _converse.vcards.browserStorage = new Backbone.BrowserStorage[_converse.config.get('storage')](id);
  123. _converse.vcards.fetch();
  124. }
  125. _converse.api.listen.on('sessionInitialized', _converse.initVCardCollection);
  126. _converse.on('addClientFeatures', () => {
  127. _converse.api.disco.own.features.add(Strophe.NS.VCARD);
  128. });
  129. _.extend(_converse.api, {
  130. /**
  131. * The XEP-0054 VCard API
  132. *
  133. * This API lets you access and update user VCards
  134. *
  135. * @namespace _converse.api.vcard
  136. * @memberOf _converse.api
  137. */
  138. 'vcard': {
  139. /**
  140. * Enables setting new values for a VCard.
  141. *
  142. * @method _converse.api.vcard.set
  143. * @param {string} jid The JID for which the VCard should be set
  144. * @param {object} data A map of VCard keys and values
  145. * @example
  146. * _converse.api.vcard.set({
  147. * 'jid': _converse.bare_jid,
  148. * 'fn': 'John Doe',
  149. * 'nickname': 'jdoe'
  150. * }).then(() => {
  151. * // Succes
  152. * }).catch(() => {
  153. * // Failure
  154. * }).
  155. */
  156. 'set' (jid, data) {
  157. return setVCard(jid, data);
  158. },
  159. /**
  160. * @method _converse.api.vcard.get
  161. * @param {Backbone.Model|string} model Either a `Backbone.Model` instance, or a string JID.
  162. * If a `Backbone.Model` instance is passed in, then it must have either a `jid`
  163. * attribute or a `muc_jid` attribute.
  164. * @param {boolean} [force] A boolean indicating whether the vcard should be
  165. * fetched even if it's been fetched before.
  166. * @returns {promise} A Promise which resolves with the VCard data for a particular JID or for
  167. * a `Backbone.Model` instance which represents an entity with a JID (such as a roster contact,
  168. * chat or chatroom occupant).
  169. *
  170. * @example
  171. * _converse.api.waitUntil('rosterContactsFetched').then(() => {
  172. * _converse.api.vcard.get('someone@example.org').then(
  173. * (vcard) => {
  174. * // Do something with the vcard...
  175. * }
  176. * );
  177. * });
  178. */
  179. 'get' (model, force) {
  180. if (_.isString(model)) {
  181. return getVCard(_converse, model);
  182. } else if (force ||
  183. !model.get('vcard_updated') ||
  184. !moment(model.get('vcard_error')).isSame(new Date(), "day")) {
  185. const jid = model.get('jid');
  186. if (!jid) {
  187. throw new Error("No JID to get vcard for!");
  188. }
  189. return getVCard(_converse, jid);
  190. } else {
  191. return Promise.resolve({});
  192. }
  193. },
  194. /**
  195. * Fetches the VCard associated with a particular `Backbone.Model` instance
  196. * (by using its `jid` or `muc_jid` attribute) and then updates the model with the
  197. * returned VCard data.
  198. *
  199. * @method _converse.api.vcard.update
  200. * @param {Backbone.Model} model A `Backbone.Model` instance
  201. * @param {boolean} [force] A boolean indicating whether the vcard should be
  202. * fetched again even if it's been fetched before.
  203. * @returns {promise} A promise which resolves once the update has completed.
  204. * @example
  205. * _converse.api.waitUntil('rosterContactsFetched').then(() => {
  206. * const chatbox = _converse.chatboxes.getChatBox('someone@example.org');
  207. * _converse.api.vcard.update(chatbox);
  208. * });
  209. */
  210. 'update' (model, force) {
  211. return this.get(model, force)
  212. .then(vcard => {
  213. delete vcard['stanza']
  214. model.save(vcard);
  215. });
  216. }
  217. }
  218. });
  219. }
  220. });
  221. }));