utils.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /**
  2. * @typedef {import('./contacts').default} RosterContacts
  3. */
  4. import _converse from '../../shared/_converse.js';
  5. import api from '../../shared/api/index.js';
  6. import converse from '../../shared/api/public.js';
  7. import log from "../../log.js";
  8. import { Model } from '@converse/skeletor';
  9. import { RosterFilter } from '../../plugins/roster/filter.js';
  10. import { PRIVATE_CHAT_TYPE } from "../../shared/constants";
  11. import { initStorage } from '../../utils/storage.js';
  12. import { shouldClearCache } from '../../utils/session.js';
  13. const { $pres } = converse.env;
  14. function initRoster () {
  15. // Initialize the collections that represent the roster contacts and groups
  16. const roster = new _converse.exports.RosterContacts();
  17. Object.assign(_converse, { roster }); // XXX Deprecated
  18. Object.assign(_converse.state, { roster });
  19. const bare_jid = _converse.session.get('bare_jid');
  20. let id = `converse.contacts-${bare_jid}`;
  21. initStorage(roster, id);
  22. const roster_filter = new RosterFilter();
  23. Object.assign(_converse, { roster_filter }); // XXX Deprecated
  24. Object.assign(_converse.state, { roster_filter });
  25. roster_filter.id = `_converse.rosterfilter-${bare_jid}`;
  26. initStorage(roster_filter, roster_filter.id);
  27. roster_filter.fetch();
  28. id = `converse-roster-model-${bare_jid}`;
  29. roster.data = new Model();
  30. roster.data.id = id;
  31. initStorage(roster.data, id);
  32. roster.data.fetch();
  33. /**
  34. * Triggered once the `RosterContacts`
  35. * been created, but not yet populated with data.
  36. * This event is useful when you want to create views for these collections.
  37. * @event _converse#chatBoxMaximized
  38. * @example _converse.api.listen.on('rosterInitialized', () => { ... });
  39. * @example _converse.api.waitUntil('rosterInitialized').then(() => { ... });
  40. */
  41. api.trigger('rosterInitialized', roster);
  42. }
  43. /**
  44. * Fetch all the roster groups, and then the roster contacts.
  45. * Emit an event after fetching is done in each case.
  46. * @param {boolean} ignore_cache - If set to to true, the local cache
  47. * will be ignored it's guaranteed that the XMPP server
  48. * will be queried for the roster.
  49. */
  50. async function populateRoster (ignore_cache=false) {
  51. const connection = api.connection.get();
  52. if (ignore_cache) {
  53. connection.send_initial_presence = true;
  54. }
  55. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  56. try {
  57. await roster.fetchRosterContacts();
  58. api.trigger('rosterContactsFetched', roster);
  59. } catch (reason) {
  60. log.error(reason);
  61. } finally {
  62. connection.send_initial_presence && api.user.presence.send();
  63. }
  64. }
  65. function updateUnreadCounter (chatbox) {
  66. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  67. const contact = roster?.get(chatbox.get('jid'));
  68. contact?.save({'num_unread': chatbox.get('num_unread')});
  69. }
  70. let presence_ref;
  71. function registerPresenceHandler () {
  72. unregisterPresenceHandler();
  73. const connection = api.connection.get();
  74. presence_ref = connection.addHandler(presence => {
  75. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  76. roster.presenceHandler(presence);
  77. return true;
  78. }, null, 'presence', null);
  79. }
  80. export function unregisterPresenceHandler () {
  81. if (presence_ref) {
  82. const connection = api.connection.get();
  83. connection.deleteHandler(presence_ref);
  84. presence_ref = null;
  85. }
  86. }
  87. async function clearPresences () {
  88. await _converse.state.presences?.clearStore();
  89. }
  90. /**
  91. * Roster specific event handler for the clearSession event
  92. */
  93. export async function onClearSession () {
  94. await clearPresences();
  95. if (shouldClearCache(_converse)) {
  96. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  97. if (roster) {
  98. roster.data?.destroy();
  99. await roster.clearStore();
  100. delete _converse.state.roster;
  101. Object.assign(_converse, { roster: undefined }); // XXX DEPRECATED
  102. }
  103. }
  104. }
  105. /**
  106. * Roster specific event handler for the presencesInitialized event
  107. * @param {Boolean} reconnecting
  108. */
  109. export function onPresencesInitialized (reconnecting) {
  110. if (reconnecting) {
  111. /**
  112. * Similar to `rosterInitialized`, but instead pertaining to reconnection.
  113. * This event indicates that the roster and its groups are now again
  114. * available after Converse.js has reconnected.
  115. * @event _converse#rosterReadyAfterReconnection
  116. * @example _converse.api.listen.on('rosterReadyAfterReconnection', () => { ... });
  117. */
  118. api.trigger('rosterReadyAfterReconnection');
  119. } else {
  120. initRoster();
  121. }
  122. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  123. roster.onConnected();
  124. registerPresenceHandler();
  125. populateRoster(!api.connection.get().restored);
  126. }
  127. /**
  128. * Roster specific event handler for the statusInitialized event
  129. * @param { Boolean } reconnecting
  130. */
  131. export async function onStatusInitialized (reconnecting) {
  132. if (reconnecting) {
  133. // When reconnecting and not resuming a previous session,
  134. // we clear all cached presence data, since it might be stale
  135. // and we'll receive new presence updates
  136. !api.connection.get().hasResumed() && (await clearPresences());
  137. } else {
  138. const presences = new _converse.exports.Presences();
  139. Object.assign(_converse, { presences });
  140. Object.assign(_converse.state, { presences });
  141. const bare_jid = _converse.session.get('bare_jid');
  142. const id = `converse.presences-${bare_jid}`;
  143. initStorage(presences, id, 'session');
  144. // We might be continuing an existing session, so we fetch
  145. // cached presence data.
  146. presences.fetch();
  147. }
  148. /**
  149. * Triggered once the _converse.Presences collection has been
  150. * initialized and its cached data fetched.
  151. * Returns a boolean indicating whether this event has fired due to
  152. * Converse having reconnected.
  153. * @event _converse#presencesInitialized
  154. * @type {boolean}
  155. * @example _converse.api.listen.on('presencesInitialized', reconnecting => { ... });
  156. */
  157. api.trigger('presencesInitialized', reconnecting);
  158. }
  159. /**
  160. * Roster specific event handler for the chatBoxesInitialized event
  161. */
  162. export function onChatBoxesInitialized () {
  163. const { chatboxes } = _converse.state;
  164. chatboxes.on('change:num_unread', updateUnreadCounter);
  165. chatboxes.on('add', chatbox => {
  166. if (chatbox.get('type') === PRIVATE_CHAT_TYPE) {
  167. chatbox.setModelContact(chatbox.get('jid'));
  168. }
  169. });
  170. }
  171. /**
  172. * Roster specific handler for the rosterContactsFetched promise
  173. */
  174. export function onRosterContactsFetched () {
  175. const roster = /** @type {RosterContacts} */(_converse.state.roster);
  176. roster.on('add', contact => {
  177. // When a new contact is added, check if we already have a
  178. // chatbox open for it, and if so attach it to the chatbox.
  179. const chatbox = _converse.state.chatboxes.findWhere({ 'jid': contact.get('jid') });
  180. chatbox?.setModelContact(contact.get('jid'));
  181. });
  182. }
  183. /**
  184. * Reject or cancel another user's subscription to our presence updates.
  185. * @function rejectPresenceSubscription
  186. * @param {String} jid - The Jabber ID of the user whose subscription is being canceled
  187. * @param {String} message - An optional message to the user
  188. */
  189. export function rejectPresenceSubscription (jid, message) {
  190. const pres = $pres({to: jid, type: "unsubscribed"});
  191. if (message && message !== "") { pres.c("status").t(message); }
  192. api.send(pres);
  193. }