utils.js 7.6 KB

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