converse.js
Version:
Browser based XMPP chat client
227 lines (205 loc) • 7.27 kB
JavaScript
/**
* @typedef {import('../../plugins/muc/message').default} MUCMessage
* @typedef {import('../../plugins/status/profile').default} Profile
* @typedef {import('../../plugins/vcard/vcards').default} VCards
* @typedef {import('../../plugins/vcard/vcard').default} VCard
* @typedef {import('../../shared/model-with-contact.js').default} ModelWithContact
* @typedef {import('../muc/occupant.js').default} MUCOccupant
* @typedef {import('@converse/skeletor/src/types/helpers.js').Model} Model
*/
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 { shouldClearCache } from '../../utils/session.js';
import { isElement } from '../../utils/html.js';
import { parseErrorStanza } from '../../shared/parsers.js';
import {parseVCardResultStanza} from './parsers.js';
const { Strophe, $iq, sizzle, stx } = converse.env;
Strophe.addNamespace('VCARD_UPDATE', 'vcard-temp:x:update');
/**
* @param {"get"|"set"|"result"} type
* @param {string} jid
* @param {Element} [vcard_el]
*/
export function createStanza(type, jid, vcard_el) {
const iq = $iq(jid ? { 'type': type, 'to': jid } : { 'type': type });
if (!vcard_el) {
iq.c('vCard', { 'xmlns': Strophe.NS.VCARD });
} else {
iq.cnode(vcard_el);
}
return iq;
}
/**
* @param {MUCOccupant} occupant
*/
export function onOccupantAvatarChanged(occupant) {
const hash = occupant.get('image_hash');
const vcards = [];
if (occupant.get('jid')) {
vcards.push(_converse.state.vcards.get(occupant.get('jid')));
}
vcards.push(_converse.state.vcards.get(occupant.get('from')));
vcards.forEach((v) => hash && v && v?.get('image_hash') !== hash && api.vcard.update(v, true));
}
/**
* @param {Model|MUCOccupant|MUCMessage} model
* @param {boolean} [lazy_load=false]
* @returns {Promise<VCard|null>}
*/
export async function getVCardForModel(model, lazy_load = false) {
await api.waitUntil('VCardsInitialized');
let vcard;
if (model instanceof _converse.exports.MUCOccupant) {
vcard = await getVCardForOccupant(/** @type {MUCOccupant} */ (model), lazy_load);
} else if (model instanceof _converse.exports.MUCMessage) {
vcard = await getVCardForMUCMessage(/** @type {MUCMessage} */ (model), lazy_load);
} else {
let jid;
if (model instanceof _converse.exports.Message) {
if (['error', 'info'].includes(model.get('type'))) {
return;
}
jid = Strophe.getBareJidFromJid(model.get('from'));
} else {
jid = model.get('jid');
}
if (!jid) {
log.warn(`Could not set VCard on model because no JID found!`);
return null;
}
const { vcards } = _converse.state;
vcard = vcards.get(jid) || vcards.create({ jid }, { lazy_load });
}
if (vcard) {
vcard.on('change', () => model.trigger('vcard:change'));
}
return vcard;
}
/**
* @param {MUCOccupant} occupant
* @param {boolean} [lazy_load=false]
* @returns {Promise<VCard|null>}
*/
export async function getVCardForOccupant(occupant, lazy_load = true) {
await api.waitUntil('VCardsInitialized');
const { vcards, profile } = _converse.state;
const muc = occupant?.collection?.chatroom;
const nick = occupant.get('nick');
if (nick && muc?.get('nick') === nick) {
return profile.vcard;
} else {
const jid = occupant.get('jid') || occupant.get('from');
if (jid) {
return vcards.get(jid) || vcards.create({ jid }, { lazy_load });
} else {
log.debug(`Could not get VCard for occupant because no JID found!`);
return null;
}
}
}
/**
* @param {MUCMessage} message
* @param {boolean} [lazy_load=true]
* @returns {Promise<VCard|null>}
*/
async function getVCardForMUCMessage(message, lazy_load = true) {
if (['error', 'info'].includes(message.get('type'))) return;
await api.waitUntil('VCardsInitialized');
const { vcards, profile } = _converse.state;
const muc = message?.collection?.chatbox;
const nick = Strophe.getResourceFromJid(message.get('from'));
if (nick && muc?.get('nick') === nick) {
return profile.vcard;
} else {
const jid = message.occupant?.get('jid') || message.get('from');
if (jid) {
return vcards.get(jid) || vcards.create({ jid }, { lazy_load });
} else {
log.warn(`Could not get VCard for message because no JID found! msgid: ${message.get('msgid')}`);
return null;
}
}
}
export function clearVCardsSession() {
if (shouldClearCache(_converse)) {
api.promises.add('VCardsInitialized');
if (_converse.state.vcards) {
_converse.state.vcards.clearStore();
Object.assign(_converse, { vcards: undefined }); // XXX DEPRECATED
delete _converse.state.vcards;
}
}
}
/**
* @param {string} jid
*/
export async function fetchVCard(jid) {
const bare_jid = _converse.session.get('bare_jid');
const to = Strophe.getBareJidFromJid(jid) === bare_jid ? null : jid;
let iq;
try {
iq = await api.sendIQ(createStanza('get', to));
} catch (error) {
const { message: error_msg } = (isElement(error) ? await parseErrorStanza(error) : error) ?? {};
return {
jid,
stanza: isElement(error) ? error : null, // TODO: remove?
error: error_msg,
vcard_error: new Date().toISOString(),
};
}
return parseVCardResultStanza(iq);
}
/**
* @param {Element} pres
*/
async function handleVCardUpdatePresence(pres) {
await api.waitUntil('VCardsInitialized');
const photo = sizzle(`x[xmlns="${Strophe.NS.VCARD_UPDATE}"] photo`, pres).pop();
if (photo) {
const avatar_hash = photo.textContent;
const from_jid = Strophe.getBareJidFromJid(pres.getAttribute('from'));
const vcard = await _converse.state.vcards.get(from_jid);
if (vcard?.get('image_hash') !== avatar_hash) {
api.vcard.update(from_jid, true).catch((e) => log.error(e));
}
}
}
let presence_ref;
export function unregisterPresenceHandler() {
if (presence_ref) {
const connection = api.connection.get();
connection.deleteHandler(presence_ref);
presence_ref = null;
}
}
export function registerPresenceHandler() {
// unregisterPresenceHandler();
const connection = api.connection.get();
presence_ref = connection.addHandler(
/** @param {Element} pres */
(pres) => {
try {
handleVCardUpdatePresence(pres);
} catch (e) {
log.error(e);
}
return true;
},
null,
'presence',
null
);
}
/**
* @param {import('strophe.js').Builder} stanza
*/
export function updatePresence(stanza) {
if (sizzle(`x[xmlns=${Strophe.NS.VCARD_UPDATE}"]`, stanza.root()).length === 0) {
const node = stx`<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>`;
stanza.root().cnode(node).up();
}
return stanza;
}