utils.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { ROLES } from './constants.js';
  2. import { _converse, api, converse, log } from '@converse/headless';
  3. import { safeSave } from '@converse/headless/utils/core.js';
  4. const { Strophe, sizzle, u } = converse.env;
  5. export function getAutoFetchedAffiliationLists () {
  6. const affs = api.settings.get('muc_fetch_members');
  7. return Array.isArray(affs) ? affs : affs ? ['member', 'admin', 'owner'] : [];
  8. }
  9. /**
  10. * Given an occupant model, see which roles may be assigned to that user.
  11. * @param { Model } occupant
  12. * @returns { Array<('moderator'|'participant'|'visitor')> } - An array of assignable roles
  13. */
  14. export function getAssignableRoles (occupant) {
  15. let disabled = api.settings.get('modtools_disable_assign');
  16. if (!Array.isArray(disabled)) {
  17. disabled = disabled ? ROLES : [];
  18. }
  19. if (occupant.get('role') === 'moderator') {
  20. return ROLES.filter(r => !disabled.includes(r));
  21. } else {
  22. return [];
  23. }
  24. }
  25. export function registerDirectInvitationHandler () {
  26. _converse.connection.addHandler(
  27. message => {
  28. _converse.onDirectMUCInvitation(message);
  29. return true;
  30. },
  31. 'jabber:x:conference',
  32. 'message'
  33. );
  34. }
  35. export function disconnectChatRooms () {
  36. /* When disconnecting, mark all groupchats as
  37. * disconnected, so that they will be properly entered again
  38. * when fetched from session storage.
  39. */
  40. return _converse.chatboxes
  41. .filter(m => m.get('type') === _converse.CHATROOMS_TYPE)
  42. .forEach(m => m.session.save({ 'connection_status': converse.ROOMSTATUS.DISCONNECTED }));
  43. }
  44. export async function onWindowStateChanged (data) {
  45. if (data.state === 'visible' && api.connection.connected()) {
  46. const rooms = await api.rooms.get();
  47. rooms.forEach(room => room.rejoinIfNecessary());
  48. }
  49. }
  50. export async function routeToRoom (jid) {
  51. if (!u.isValidMUCJID(jid)) {
  52. return log.warn(`invalid jid "${jid}" provided in url fragment`);
  53. }
  54. await api.waitUntil('roomsAutoJoined');
  55. if (api.settings.get('allow_bookmarks')) {
  56. await api.waitUntil('bookmarksInitialized');
  57. }
  58. api.rooms.open(jid);
  59. }
  60. /* Opens a groupchat, making sure that certain attributes
  61. * are correct, for example that the "type" is set to
  62. * "chatroom".
  63. */
  64. export async function openChatRoom (jid, settings) {
  65. settings.type = _converse.CHATROOMS_TYPE;
  66. settings.id = jid;
  67. const chatbox = await api.rooms.get(jid, settings, true);
  68. chatbox.maybeShow(true);
  69. return chatbox;
  70. }
  71. /**
  72. * A direct MUC invitation to join a groupchat has been received
  73. * See XEP-0249: Direct MUC invitations.
  74. * @private
  75. * @method _converse.ChatRoom#onDirectMUCInvitation
  76. * @param { Element } message - The message stanza containing the invitation.
  77. */
  78. export async function onDirectMUCInvitation (message) {
  79. const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
  80. from = Strophe.getBareJidFromJid(message.getAttribute('from')),
  81. room_jid = x_el.getAttribute('jid'),
  82. reason = x_el.getAttribute('reason');
  83. let result;
  84. if (api.settings.get('auto_join_on_invite')) {
  85. result = true;
  86. } else {
  87. // Invite request might come from someone not your roster list
  88. const contact = _converse.roster.get(from)?.getDisplayName() ?? from;
  89. /**
  90. * *Hook* which is used to gather confirmation whether a direct MUC
  91. * invitation should be accepted or not.
  92. *
  93. * It's meant for consumers of `@converse/headless` to subscribe to
  94. * this hook and then ask the user to confirm.
  95. *
  96. * @event _converse#confirmDirectMUCInvitation
  97. */
  98. result = await api.hook('confirmDirectMUCInvitation', { contact, reason, jid: room_jid }, false);
  99. }
  100. if (result) {
  101. const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') });
  102. if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
  103. _converse.chatboxes.get(room_jid).rejoin();
  104. }
  105. }
  106. }
  107. export function getDefaultMUCNickname () {
  108. // XXX: if anything changes here, update the docs for the
  109. // locked_muc_nickname setting.
  110. if (!_converse.xmppstatus) {
  111. throw new Error(
  112. "Can't call _converse.getDefaultMUCNickname before the statusInitialized has been fired."
  113. );
  114. }
  115. const nick = _converse.xmppstatus.getNickname();
  116. if (nick) {
  117. return nick;
  118. } else if (api.settings.get('muc_nickname_from_jid')) {
  119. return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
  120. }
  121. }
  122. /**
  123. * Determines info message visibility based on
  124. * muc_show_info_messages configuration setting
  125. * @param {*} code
  126. * @memberOf _converse
  127. */
  128. export function isInfoVisible (code) {
  129. const info_messages = api.settings.get('muc_show_info_messages');
  130. if (info_messages.includes(code)) {
  131. return true;
  132. }
  133. return false;
  134. }
  135. /**
  136. * Automatically join groupchats, based on the
  137. * "auto_join_rooms" configuration setting, which is an array
  138. * of strings (groupchat JIDs) or objects (with groupchat JID and other settings).
  139. */
  140. export async function autoJoinRooms () {
  141. await Promise.all(
  142. api.settings.get('auto_join_rooms').map(muc => {
  143. if (typeof muc === 'string') {
  144. if (_converse.chatboxes.where({ 'jid': muc }).length) {
  145. return Promise.resolve();
  146. }
  147. return api.rooms.open(muc);
  148. } else if (muc instanceof Object) {
  149. return api.rooms.open(muc.jid, { ...muc });
  150. } else {
  151. log.error('Invalid muc criteria specified for "auto_join_rooms"');
  152. return Promise.resolve();
  153. }
  154. })
  155. );
  156. /**
  157. * Triggered once any rooms that have been configured to be automatically joined,
  158. * specified via the _`auto_join_rooms` setting, have been entered.
  159. * @event _converse#roomsAutoJoined
  160. * @example _converse.api.listen.on('roomsAutoJoined', () => { ... });
  161. * @example _converse.api.waitUntil('roomsAutoJoined').then(() => { ... });
  162. */
  163. api.trigger('roomsAutoJoined');
  164. }
  165. export function onAddClientFeatures () {
  166. api.disco.own.features.add(Strophe.NS.MUC);
  167. if (api.settings.get('allow_muc_invitations')) {
  168. api.disco.own.features.add('jabber:x:conference'); // Invites
  169. }
  170. }
  171. export function onBeforeTearDown () {
  172. _converse.chatboxes
  173. .where({ 'type': _converse.CHATROOMS_TYPE })
  174. .forEach(muc => safeSave(muc.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED }));
  175. }
  176. export function onStatusInitialized () {
  177. window.addEventListener(_converse.unloadevent, () => {
  178. const using_websocket = api.connection.isType('websocket');
  179. if (
  180. using_websocket &&
  181. (!api.settings.get('enable_smacks') || !_converse.session.get('smacks_stream_id'))
  182. ) {
  183. // For non-SMACKS websocket connections, or non-resumeable
  184. // connections, we disconnect all chatrooms when the page unloads.
  185. // See issue #1111
  186. disconnectChatRooms();
  187. }
  188. });
  189. }
  190. export function onBeforeResourceBinding () {
  191. _converse.connection.addHandler(
  192. stanza => {
  193. const muc_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
  194. if (!_converse.chatboxes.get(muc_jid)) {
  195. api.waitUntil('chatBoxesFetched').then(async () => {
  196. const muc = _converse.chatboxes.get(muc_jid);
  197. if (muc) {
  198. await muc.initialized;
  199. muc.message_handler.run(stanza);
  200. }
  201. });
  202. }
  203. return true;
  204. },
  205. null,
  206. 'message',
  207. 'groupchat'
  208. );
  209. }
  210. Object.assign(_converse, { getAssignableRoles });