api.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /**
  2. * @typedef {import('@converse/skeletor').Model} Model
  3. */
  4. import log from "@converse/log";
  5. import _converse from '../../shared/_converse.js';
  6. import api from '../../shared/api/index.js';
  7. import converse from "../../shared/api/public.js";
  8. import { createStanza, fetchVCard } from './utils.js';
  9. const { dayjs, u } = converse.env;
  10. /**
  11. * @typedef {Object} VCardData
  12. * @property {string} [VCardData.fn]
  13. * @property {string} [VCardData.nickname]
  14. * @property {string} [VCardData.role]
  15. * @property {string} [VCardData.email]
  16. * @property {string} [VCardData.url]
  17. * @property {string} [VCardData.image_type]
  18. * @property {string} [VCardData.image]
  19. */
  20. export default {
  21. /**
  22. * The XEP-0054 VCard API
  23. *
  24. * This API lets you access and update user VCards
  25. *
  26. * @namespace _converse.api.vcard
  27. * @memberOf _converse.api
  28. */
  29. vcard: {
  30. /**
  31. * Enables setting new values for a VCard.
  32. *
  33. * Sends out an IQ stanza to set the user's VCard and if
  34. * successful, it updates the {@link _converse.VCard}
  35. * for the passed in JID.
  36. *
  37. * @method _converse.api.vcard.set
  38. * @param {string} jid The JID for which the VCard should be set
  39. * @param {VCardData} data A map of VCard keys and values
  40. *
  41. * @example
  42. * let jid = _converse.bare_jid;
  43. * _converse.api.vcard.set( jid, {
  44. * 'fn': 'John Doe',
  45. * 'nickname': 'jdoe'
  46. * }).then(() => {
  47. * // Succes
  48. * }).catch((e) => {
  49. * // Failure, e is your error object
  50. * }).
  51. */
  52. async set (jid, data) {
  53. api.waitUntil('VCardsInitialized');
  54. if (!jid) {
  55. throw Error("No jid provided for the VCard data");
  56. }
  57. const div = document.createElement('div');
  58. const vcard_el = u.toStanza(`
  59. <vCard xmlns="vcard-temp">
  60. <FN>${data.fn ?? ''}</FN>
  61. <NICKNAME>${data.nickname ?? ''}</NICKNAME>
  62. <URL>${data.url ?? ''}</URL>
  63. <ROLE>${data.role ?? ''}</ROLE>
  64. <EMAIL><INTERNET/><PREF/><USERID>${data.email ?? ''}</USERID></EMAIL>
  65. <PHOTO>
  66. <TYPE>${data.image_type ?? ''}</TYPE>
  67. <BINVAL>${data.image ?? ''}</BINVAL>
  68. </PHOTO>
  69. </vCard>`, div);
  70. let result;
  71. try {
  72. result = await api.sendIQ(createStanza("set", jid, vcard_el));
  73. } catch (e) {
  74. throw (e);
  75. }
  76. await api.vcard.update(jid, true);
  77. return result;
  78. },
  79. /**
  80. * @method _converse.api.vcard.get
  81. * @param {Model|string} model Either a `Model` instance, or a string JID.
  82. * If a `Model` instance is passed in, then it must have either a `jid`
  83. * attribute or a `muc_jid` attribute.
  84. * @param {boolean} [force] A boolean indicating whether the vcard should be
  85. * fetched from the server even if it's been fetched before.
  86. * @returns {Promise<import("./types").VCardResult|null>} A Promise which resolves
  87. * with the VCard data for a particular JID or for a `Model` instance which
  88. * represents an entity with a JID (such as a roster contact, chat or chatroom occupant).
  89. *
  90. * @example
  91. * const { api } = _converse;
  92. * api.waitUntil('rosterContactsFetched').then(() => {
  93. * api.vcard.get('someone@example.org').then(
  94. * (vcard) => {
  95. * // Do something with the vcard...
  96. * }
  97. * );
  98. * });
  99. */
  100. async get(model, force) {
  101. api.waitUntil("VCardsInitialized");
  102. if (typeof model === "string") return fetchVCard(model);
  103. const error_date = model.get("vcard_error");
  104. if (error_date) {
  105. // For a VCard fetch that returned an error, we check how long ago
  106. // it was fetched. If it was longer ago than the last 21 days plus
  107. // some jitter (to prevent an IQ fetch flood), we try again.
  108. const { random, round } = Math;
  109. const subtract_flag = round(random());
  110. const recent_date = dayjs()
  111. .subtract(21, "days")
  112. .subtract(round(random() * 24) * subtract_flag, "hours")
  113. .add(round(random() * 24) * (!subtract_flag ? 1 : 0), "hours");
  114. const tried_recently = dayjs(error_date).isAfter(recent_date);
  115. if (!force && tried_recently) return null;
  116. }
  117. const vcard_updated = model.get("vcard_updated");
  118. if (vcard_updated) {
  119. // For a successful VCard fetch, we check how long ago it was fetched.
  120. // If it was longer ago than the last 7 days plus some jitter
  121. // (to prevent an IQ fetch flood), we try again.
  122. const { random, round } = Math;
  123. const subtract_flag = round(random());
  124. const recent_date = dayjs()
  125. .subtract(7, "days")
  126. .subtract(round(random() * 24) * subtract_flag, "hours")
  127. .add(round(random() * 24) * (!subtract_flag ? 1 : 0), "hours");
  128. const updated_recently = dayjs(vcard_updated).isAfter(recent_date);
  129. if (!force && updated_recently) return null;
  130. }
  131. const jid = model.get("jid");
  132. if (!jid) {
  133. log.error("No JID to get vcard for");
  134. return null;
  135. }
  136. return fetchVCard(jid);
  137. },
  138. /**
  139. * Fetches the VCard associated with a particular `Model` instance
  140. * (by using its `jid` or `muc_jid` attribute) and then updates the model with the
  141. * returned VCard data.
  142. *
  143. * @method _converse.api.vcard.update
  144. * @param {Model} model A `Model` instance
  145. * @param {boolean} [force] A boolean indicating whether the vcard should be
  146. * fetched again even if it's been fetched before.
  147. * @returns {promise} A promise which resolves once the update has completed.
  148. * @example
  149. * const { api } = _converse;
  150. * api.waitUntil('rosterContactsFetched').then(async () => {
  151. * const chatbox = await api.chats.get('someone@example.org');
  152. * api.vcard.update(chatbox);
  153. * });
  154. */
  155. async update (model, force) {
  156. api.waitUntil('VCardsInitialized');
  157. const data = await this.get(model, force);
  158. if (data === null) {
  159. log.debug('api.vcard.update: null data returned, not updating the vcard');
  160. return;
  161. }
  162. model = typeof model === 'string' ? _converse.state.vcards.get(model) : model;
  163. if (!model) {
  164. log.error(`Could not find a VCard model for ${model}`);
  165. return;
  166. }
  167. if (Object.keys(data).length) {
  168. delete data['stanza']
  169. u.safeSave(model, data);
  170. }
  171. }
  172. }
  173. }