UNPKG

converse.js

Version:
269 lines (246 loc) 9.32 kB
import _converse from '../../shared/_converse.js'; import api from '../../shared/api/index.js'; import converse from '../../shared/api/public.js'; import log from '@converse/log'; import { MUC_ROLE_WEIGHTS } from './constants.js'; import { safeSave } from '../../utils/init.js'; import { CHATROOMS_TYPE } from '../../shared/constants.js'; import { getUnloadEvent } from '../../utils/session.js'; const { Strophe, sizzle, u } = converse.env; /** * @returns {Promise<string|undefined>} */ export async function getDefaultMUCService() { let muc_service = api.settings.get('muc_domain') || _converse.session.get('default_muc_service'); if (!muc_service) { const domain = _converse.session.get('domain'); const items = await api.disco.entities.items(domain); for (const item of items) { if (await api.disco.features.has(Strophe.NS.MUC, item.get('jid'))) { muc_service = item.get('jid'); _converse.session.save({ default_muc_service: muc_service }); break; } } } return muc_service; } /** * @param {import('@converse/skeletor').Model} model */ export function isChatRoom(model) { return model?.get('type') === 'chatroom'; } export function shouldCreateGroupchatMessage(attrs) { return attrs.nick && (u.shouldCreateMessage(attrs) || attrs.is_tombstone); } /** * @param {import('./occupant').default} occupant1 * @param {import('./occupant').default} occupant2 */ export function occupantsComparator(occupant1, occupant2) { const role1 = occupant1.get('role') || 'none'; const role2 = occupant2.get('role') || 'none'; if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) { const nick1 = occupant1.getDisplayName().toLowerCase(); const nick2 = occupant2.getDisplayName().toLowerCase(); return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0; } else { return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1; } } export function registerDirectInvitationHandler() { api.connection.get().addHandler( /** @param {Element} message */ (message) => { _converse.exports.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.state.chatboxes .filter((m) => m.get('type') === CHATROOMS_TYPE) .forEach((m) => m.session.save({ 'connection_status': converse.ROOMSTATUS.DISCONNECTED })); } export async function onWindowStateChanged() { if (!document.hidden && api.connection.connected()) { const rooms = await api.rooms.get(); rooms.forEach((room) => room.rejoinIfNecessary()); } } /** * @param {Event} [event] */ export async function routeToRoom(event) { if (!location.hash.startsWith('#converse/room?jid=')) { return; } event?.preventDefault(); const jid = location.hash.split('=').pop(); 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, {}, true); } /** * Opens a groupchat, making sure that certain attributes * are correct, for example that the "type" is set to * "chatroom". * @param {string} jid * @param {Object} settings */ export async function openChatRoom(jid, settings) { settings.type = 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. * @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.state.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.state.chatboxes.get(room_jid).rejoin(); } } } export function getDefaultMUCNickname() { // XXX: if anything changes here, update the docs for the // locked_muc_nickname setting. const { profile } = _converse.state; if (!profile) { log.error('Called getDefaultMUCNickname before statusInitialized has been fired.'); return ''; } const nick = profile.getNickname(); if (nick) { return nick; } else if (api.settings.get('muc_nickname_from_jid')) { const bare_jid = _converse.session.get('bare_jid'); return Strophe.unescapeNode(Strophe.getNodeFromJid(bare_jid)); } } /** * Determines info message visibility based on * muc_show_info_messages configuration setting * @param {import('./types').MUCStatusCode} 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.state.chatboxes.where({ 'jid': muc }).length) { return Promise.resolve(); } return api.rooms.open(muc); } else if (muc instanceof Object) { 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.state.chatboxes .where({ 'type': CHATROOMS_TYPE }) .forEach((muc) => safeSave(muc.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED })); } export function onStatusInitialized() { window.addEventListener(getUnloadEvent(), () => { 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() { api.connection.get().addHandler( /** @param {Element} stanza */ (stanza) => { const muc_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from')); if (!_converse.state.chatboxes.get(muc_jid)) { api.waitUntil('chatBoxesFetched').then(async () => { const muc = _converse.state.chatboxes.get(muc_jid); if (muc) { await muc.initialized; muc.message_handler.run(stanza); } }); } return true; }, null, 'message', 'groupchat' ); }