utils.js 7.9 KB

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