Set jid as the id attribute of the VCards collection

This reduces lookup time for vcards greatly, since we don't can access a
map of ids instead of using `findWhere` through an array of models.
This commit is contained in:
JC Brand 2022-04-15 23:51:57 +02:00
parent 7cb86638b5
commit e492885ac0
8 changed files with 97 additions and 80 deletions

View File

@ -5,6 +5,7 @@
- GIFs don't render inside unfurls and cause a TypeError - GIFs don't render inside unfurls and cause a TypeError
- Improve how the `muc_domain` setting is populated via service discovery - Improve how the `muc_domain` setting is populated via service discovery
- Remove local (non-requesting) contacts not returned from a full roster response - Remove local (non-requesting) contacts not returned from a full roster response
- Improve performance by looking up VCards via map instead of traversing an array
- #2746: Always reply to all iqs, even those not understood - #2746: Always reply to all iqs, even those not understood
- #2794: Some display problems with mobile view mode - #2794: Some display problems with mobile view mode
- #2868: Selected emoji is inserted into all open chat boxes - #2868: Selected emoji is inserted into all open chat boxes

View File

@ -23,7 +23,7 @@ const ChatRoomMessageMixin = {
this.setTimerForEphemeralMessage(); this.setTimerForEphemeralMessage();
this.setOccupant(); this.setOccupant();
/** /**
* Triggered once a {@link _converse.ChatRoomMessageInitialized} has been created and initialized. * Triggered once a { @link _converse.ChatRoomMessage } has been created and initialized.
* @event _converse#chatRoomMessageInitialized * @event _converse#chatRoomMessageInitialized
* @type { _converse.ChatRoomMessages} * @type { _converse.ChatRoomMessages}
* @example _converse.api.listen.on('chatRoomMessageInitialized', model => { ... }); * @example _converse.api.listen.on('chatRoomMessageInitialized', model => { ... });

View File

@ -15,28 +15,6 @@ const ChatRoomOccupant = Model.extend({
'states': [] 'states': []
}, },
initialize (attributes) {
this.set(Object.assign({ 'id': u.getUniqueId() }, attributes));
this.on('change:image_hash', this.onAvatarChanged, this);
},
onAvatarChanged () {
const hash = this.get('image_hash');
const vcards = [];
if (this.get('jid')) {
vcards.push(_converse.vcards.findWhere({ 'jid': this.get('jid') }));
}
vcards.push(_converse.vcards.findWhere({ 'jid': this.get('from') }));
vcards
.filter(v => v)
.forEach(vcard => {
if (hash && vcard.get('image_hash') !== hash) {
api.vcard.update(vcard, true);
}
});
},
getDisplayName () { getDisplayName () {
return this.get('nick') || this.get('jid'); return this.get('nick') || this.get('jid');
}, },

View File

@ -68,7 +68,7 @@ export default {
* If a `Model` instance is passed in, then it must have either a `jid` * If a `Model` instance is passed in, then it must have either a `jid`
* attribute or a `muc_jid` attribute. * attribute or a `muc_jid` attribute.
* @param {boolean} [force] A boolean indicating whether the vcard should be * @param {boolean} [force] A boolean indicating whether the vcard should be
* fetched even if it's been fetched before. * fetched from the server even if it's been fetched before.
* @returns {promise} A Promise which resolves with the VCard data for a particular JID or for * @returns {promise} A Promise which resolves with the VCard data for a particular JID or for
* a `Model` instance which represents an entity with a JID (such as a roster contact, * a `Model` instance which represents an entity with a JID (such as a roster contact,
* chat or chatroom occupant). * chat or chatroom occupant).
@ -119,13 +119,15 @@ export default {
*/ */
async update (model, force) { async update (model, force) {
const data = await this.get(model, force); const data = await this.get(model, force);
model = typeof model === 'string' ? _converse.vcards.findWhere({'jid': model}) : model; model = typeof model === 'string' ? _converse.vcards.get(model) : model;
if (!model) { if (!model) {
log.error(`Could not find a VCard model for ${model}`); log.error(`Could not find a VCard model for ${model}`);
return; return;
} }
delete data['stanza'] if (Object.keys(data).length) {
model.save(data); delete data['stanza']
model.save(data);
}
} }
} }
} }

View File

@ -10,6 +10,7 @@ import { _converse, api, converse } from "../../core.js";
import { import {
clearVCardsSession, clearVCardsSession,
initVCardCollection, initVCardCollection,
onOccupantAvatarChanged,
setVCardOnMUCMessage, setVCardOnMUCMessage,
setVCardOnModel, setVCardOnModel,
setVCardOnOccupant, setVCardOnOccupant,
@ -74,13 +75,14 @@ converse.plugins.add('converse-vcard', {
_converse.VCards = Collection.extend({ _converse.VCards = Collection.extend({
model: _converse.VCard, model: _converse.VCard,
initialize () { initialize () {
this.on('add', vcard => (vcard.get('jid') && api.vcard.update(vcard))); this.on('add', v => v.get('jid') && api.vcard.update(v));
} }
}); });
api.listen.on('chatRoomInitialized', m => { api.listen.on('chatRoomInitialized', m => {
setVCardOnModel(m) setVCardOnModel(m)
m.occupants.forEach(setVCardOnOccupant); m.occupants.forEach(setVCardOnOccupant);
m.occupants.on('change:image_hash', o => onOccupantAvatarChanged(o));
m.listenTo(m.occupants, 'add', setVCardOnOccupant); m.listenTo(m.occupants, 'add', setVCardOnOccupant);
}); });
api.listen.on('chatBoxInitialized', m => setVCardOnModel(m)); api.listen.on('chatBoxInitialized', m => setVCardOnModel(m));

View File

@ -42,38 +42,52 @@ export function createStanza (type, jid, vcard_el) {
} }
export function onOccupantAvatarChanged (occupant) {
const hash = occupant.get('image_hash');
const vcards = [];
if (occupant.get('jid')) {
vcards.push(_converse.vcards.get(occupant.get('jid')));
}
vcards.push(_converse.vcards.get(occupant.get('from')));
vcards.forEach(v => (hash && v?.get('image_hash') !== hash) && api.vcard.update(v, true));
}
export async function setVCardOnModel (model) { export async function setVCardOnModel (model) {
let jid; let jid;
if (model instanceof _converse.Message) { if (model instanceof _converse.Message) {
if (model.get('type') === 'error') { if (['error', 'info'].includes(model.get('type'))) {
return; return;
} }
jid = model.get('from'); jid = model.get('from');
} else { } else {
jid = model.get('jid'); jid = model.get('jid');
} }
await api.waitUntil('VCardsInitialized');
model.vcard = _converse.vcards.findWhere({'jid': jid}); if (!jid) {
if (!model.vcard) { log.warn(`Could not set VCard on model because no JID found!`);
model.vcard = _converse.vcards.create({'jid': jid}); return;
} }
await api.waitUntil('VCardsInitialized');
model.vcard = _converse.vcards.get(jid) || _converse.vcards.create({ jid });
model.vcard.on('change', () => model.trigger('vcard:change')); model.vcard.on('change', () => model.trigger('vcard:change'));
model.trigger('vcard:add'); model.trigger('vcard:add');
} }
function getVCardForChatroomOccupant (message) { function getVCardForOccupant (occupant) {
const chatbox = message?.collection?.chatbox; const muc = occupant?.collection?.chatroom;
const nick = Strophe.getResourceFromJid(message.get('from')); const nick = occupant.get('nick');
if (chatbox && chatbox.get('nick') === nick) { if (nick && muc?.get('nick') === nick) {
return _converse.xmppstatus.vcard; return _converse.xmppstatus.vcard;
} else { } else {
const jid = message.occupant && message.occupant.get('jid') || message.get('from'); const jid = occupant.get('jid') || occupant.get('from');
if (jid) { if (jid) {
return _converse.vcards.findWhere({jid}) || _converse.vcards.create({jid}); return _converse.vcards.get(jid) || _converse.vcards.create({ jid });
} else { } else {
log.error(`Could not assign VCard for message because no JID found! msgid: ${message.get('msgid')}`); log.warn(`Could not get VCard for occupant because no JID found!`);
return; return;
} }
} }
@ -81,19 +95,37 @@ function getVCardForChatroomOccupant (message) {
export async function setVCardOnOccupant (occupant) { export async function setVCardOnOccupant (occupant) {
await api.waitUntil('VCardsInitialized'); await api.waitUntil('VCardsInitialized');
occupant.vcard = getVCardForChatroomOccupant(occupant); occupant.vcard = getVCardForOccupant(occupant);
if (occupant.vcard) { if (occupant.vcard) {
occupant.vcard.on('change', () => occupant.trigger('vcard:change')); occupant.vcard.on('change', () => occupant.trigger('vcard:change'));
occupant.trigger('vcard:add'); occupant.trigger('vcard:add');
} }
} }
function getVCardForMUCMessage (message) {
const muc = message?.collection?.chatbox;
const nick = Strophe.getResourceFromJid(message.get('from'));
if (nick && muc?.get('nick') === nick) {
return _converse.xmppstatus.vcard;
} else {
const jid = message.occupant?.get('jid') || message.get('from');
if (jid) {
return _converse.vcards.get(jid) || _converse.vcards.create({ jid });
} else {
log.warn(`Could not get VCard for message because no JID found! msgid: ${message.get('msgid')}`);
return;
}
}
}
export async function setVCardOnMUCMessage (message) { export async function setVCardOnMUCMessage (message) {
if (['error', 'info'].includes(message.get('type'))) { if (['error', 'info'].includes(message.get('type'))) {
return; return;
} else { } else {
await api.waitUntil('VCardsInitialized'); await api.waitUntil('VCardsInitialized');
message.vcard = getVCardForChatroomOccupant(message); message.vcard = getVCardForMUCMessage(message);
if (message.vcard) { if (message.vcard) {
message.vcard.on('change', () => message.trigger('vcard:change')); message.vcard.on('change', () => message.trigger('vcard:change'));
message.trigger('vcard:add'); message.trigger('vcard:add');
@ -116,16 +148,16 @@ export async function initVCardCollection () {
if (_converse.session) { if (_converse.session) {
const jid = _converse.session.get('bare_jid'); const jid = _converse.session.get('bare_jid');
const status = _converse.xmppstatus; const status = _converse.xmppstatus;
status.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid}); status.vcard = vcards.get(jid) || vcards.create({'jid': jid});
if (status.vcard) { if (status.vcard) {
status.vcard.on('change', () => status.trigger('vcard:change')); status.vcard.on('change', () => status.trigger('vcard:change'));
status.trigger('vcard:add'); status.trigger('vcard:add');
} }
} }
/** /**
* Triggered as soon as the `_converse.vcards` collection has been initialized and populated from cache. * Triggered as soon as the `_converse.vcards` collection has been initialized and populated from cache.
* @event _converse#VCardsInitialized * @event _converse#VCardsInitialized
*/ */
api.trigger('VCardsInitialized'); api.trigger('VCardsInitialized');
} }

View File

@ -1,40 +1,42 @@
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { _converse } from "../../core.js"; import { _converse } from "../../core.js";
/** /**
* Represents a VCard * Represents a VCard
* @class * @class
* @namespace _converse.VCard * @namespace _converse.VCard
* @memberOf _converse * @memberOf _converse
*/ */
const VCard = Model.extend({ const VCard = Model.extend({
defaults: { idAttribute: 'jid',
'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE
},
set (key, val, options) { defaults: {
// Override Model.prototype.set to make sure that the 'image': _converse.DEFAULT_IMAGE,
// default `image` and `image_type` values are maintained. 'image_type': _converse.DEFAULT_IMAGE_TYPE
let attrs; },
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if ('image' in attrs && !attrs['image']) {
attrs['image'] = _converse.DEFAULT_IMAGE;
attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE;
return Model.prototype.set.call(this, attrs, options);
} else {
return Model.prototype.set.apply(this, arguments);
}
},
getDisplayName () { set (key, val, options) {
return this.get('nickname') || this.get('fullname') || this.get('jid'); // Override Model.prototype.set to make sure that the
} // default `image` and `image_type` values are maintained.
}); let attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if ('image' in attrs && !attrs['image']) {
attrs['image'] = _converse.DEFAULT_IMAGE;
attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE;
return Model.prototype.set.call(this, attrs, options);
} else {
return Model.prototype.set.apply(this, arguments);
}
},
getDisplayName () {
return this.get('nickname') || this.get('fullname') || this.get('jid');
}
});
export default VCard; export default VCard;

View File

@ -23,7 +23,7 @@ const OccupantModal = BaseModal.extend({
toHTML () { toHTML () {
const model = this.model ?? this.message; const model = this.model ?? this.message;
const jid = model?.get('jid'); const jid = model?.get('jid');
const vcard = _converse.vcards.findWhere({ jid }); const vcard = _converse.vcards.get(jid);
const display_name = model?.getDisplayName(); const display_name = model?.getDisplayName();
const nick = model.get('nick'); const nick = model.get('nick');
const occupant_id = model.get('occupant_id'); const occupant_id = model.get('occupant_id');