import isObject from 'lodash-es/isObject'; import log from "@converse/headless/log.js"; import { ROLES } from './constants.js'; import { _converse, api, converse } from '@converse/headless/core.js'; import { safeSave } from '@converse/headless/utils/core.js'; const { Strophe, sizzle, u } = converse.env; export function getAutoFetchedAffiliationLists () { const affs = api.settings.get('muc_fetch_members'); return Array.isArray(affs) ? affs : affs ? ['member', 'admin', 'owner'] : []; } /** * Given an occupant model, see which roles may be assigned to that user. * @param { Model } occupant * @returns { Array<('moderator'|'participant'|'visitor')> } - An array of assignable roles */ export function getAssignableRoles (occupant) { let disabled = api.settings.get('modtools_disable_assign'); if (!Array.isArray(disabled)) { disabled = disabled ? ROLES : []; } if (occupant.get('role') === 'moderator') { return ROLES.filter(r => !disabled.includes(r)); } else { return []; } } export function registerDirectInvitationHandler () { _converse.connection.addHandler( message => { _converse.onDirectMUCInvitation(message); return true; }, 'jabber:x:conference', 'message' ); } export function disconnectChatRooms () { /* When disconnecting, mark all groupchats as * disconnected, so that they will be properly entered again * when fetched from session storage. */ return _converse.chatboxes .filter(m => m.get('type') === _converse.CHATROOMS_TYPE) .forEach(m => m.session.save({ 'connection_status': converse.ROOMSTATUS.DISCONNECTED })); } export async function onWindowStateChanged (data) { if (data.state === 'visible' && api.connection.connected()) { const rooms = await api.rooms.get(); rooms.forEach(room => room.rejoinIfNecessary()); } } export async function routeToRoom (jid) { if (!u.isValidMUCJID(jid)) { return log.warn(`invalid jid "${jid}" provided in url fragment`); } await api.waitUntil('roomsAutoJoined'); if (api.settings.get('allow_bookmarks')) { await api.waitUntil('bookmarksInitialized'); } api.rooms.open(jid); } /* Opens a groupchat, making sure that certain attributes * are correct, for example that the "type" is set to * "chatroom". */ export async function openChatRoom (jid, settings) { settings.type = _converse.CHATROOMS_TYPE; settings.id = jid; const chatbox = await api.rooms.get(jid, settings, true); chatbox.maybeShow(true); return chatbox; } /** * A direct MUC invitation to join a groupchat has been received * See XEP-0249: Direct MUC invitations. * @private * @method _converse.ChatRoom#onDirectMUCInvitation * @param { Element } message - The message stanza containing the invitation. */ export async function onDirectMUCInvitation (message) { const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(), from = Strophe.getBareJidFromJid(message.getAttribute('from')), room_jid = x_el.getAttribute('jid'), reason = x_el.getAttribute('reason'); let result; if (api.settings.get('auto_join_on_invite')) { result = true; } else { // Invite request might come from someone not your roster list const contact = _converse.roster.get(from)?.getDisplayName() ?? from; /** * *Hook* which is used to gather confirmation whether a direct MUC * invitation should be accepted or not. * * It's meant for consumers of `@converse/headless` to subscribe to * this hook and then ask the user to confirm. * * @event _converse#confirmDirectMUCInvitation */ result = await api.hook('confirmDirectMUCInvitation', { contact, reason, jid: room_jid }, false); } if (result) { const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') }); if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) { _converse.chatboxes.get(room_jid).rejoin(); } } } export function getDefaultMUCNickname () { // XXX: if anything changes here, update the docs for the // locked_muc_nickname setting. if (!_converse.xmppstatus) { throw new Error( "Can't call _converse.getDefaultMUCNickname before the statusInitialized has been fired." ); } const nick = _converse.xmppstatus.getNickname(); if (nick) { return nick; } else if (api.settings.get('muc_nickname_from_jid')) { return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid)); } } /** * Determines info message visibility based on * muc_show_info_messages configuration setting * @param {*} code * @memberOf _converse */ export function isInfoVisible (code) { const info_messages = api.settings.get('muc_show_info_messages'); if (info_messages.includes(code)) { return true; } return false; } /** * Automatically join groupchats, based on the * "auto_join_rooms" configuration setting, which is an array * of strings (groupchat JIDs) or objects (with groupchat JID and other settings). */ export async function autoJoinRooms () { await Promise.all( api.settings.get('auto_join_rooms').map(muc => { if (typeof muc === 'string') { if (_converse.chatboxes.where({ 'jid': muc }).length) { return Promise.resolve(); } return api.rooms.open(muc); } else if (isObject(muc)) { return api.rooms.open(muc.jid, { ...muc }); } else { log.error('Invalid muc criteria specified for "auto_join_rooms"'); return Promise.resolve(); } }) ); /** * Triggered once any rooms that have been configured to be automatically joined, * specified via the _`auto_join_rooms` setting, have been entered. * @event _converse#roomsAutoJoined * @example _converse.api.listen.on('roomsAutoJoined', () => { ... }); * @example _converse.api.waitUntil('roomsAutoJoined').then(() => { ... }); */ api.trigger('roomsAutoJoined'); } export function onAddClientFeatures () { api.disco.own.features.add(Strophe.NS.MUC); if (api.settings.get('allow_muc_invitations')) { api.disco.own.features.add('jabber:x:conference'); // Invites } } export function onBeforeTearDown () { _converse.chatboxes .where({ 'type': _converse.CHATROOMS_TYPE }) .forEach(muc => safeSave(muc.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED })); } export function onStatusInitialized () { window.addEventListener(_converse.unloadevent, () => { const using_websocket = api.connection.isType('websocket'); if ( using_websocket && (!api.settings.get('enable_smacks') || !_converse.session.get('smacks_stream_id')) ) { // For non-SMACKS websocket connections, or non-resumeable // connections, we disconnect all chatrooms when the page unloads. // See issue #1111 disconnectChatRooms(); } }); } export function onBeforeResourceBinding () { _converse.connection.addHandler( stanza => { const muc_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from')); if (!_converse.chatboxes.get(muc_jid)) { api.waitUntil('chatBoxesFetched').then(async () => { const muc = _converse.chatboxes.get(muc_jid); if (muc) { await muc.initialized; muc.message_handler.run(stanza); } }); } return true; }, null, 'message', 'groupchat' ); } Object.assign(_converse, { getAssignableRoles });