@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
177 lines (135 loc) • 5.23 kB
text/typescript
import { AbstractPlugin, IMetaData } from '../plugin/AbstractPlugin';
import PluginAPI from '../plugin/PluginAPI';
import Avatar from '../Avatar';
import AvatarUI from '../ui/AvatarSet';
import JID from '../JID';
import { ContactType, IContact } from '@src/Contact.interface';
import Translation from '@util/Translation';
import { IAvatar } from '@src/Avatar.interface';
import { IJID } from '@src/JID.interface';
import Hash from '@util/Hash';
const MIN_VERSION = '4.0.0';
const MAX_VERSION = '99.0.0';
export default class AvatarVCardPlugin extends AbstractPlugin {
public static getId(): string {
return 'vcard-avatars';
}
public static getName(): string {
return 'vCard-based Avatars';
}
public static getMetaData(): IMetaData {
return {
description: Translation.t('setting-vcard-avatar-enable'),
xeps: [
{
id: 'XEP-0153',
name: 'vCard-Based Avatars',
version: '1.1',
},
],
};
}
constructor(pluginAPI: PluginAPI) {
super(MIN_VERSION, MAX_VERSION, pluginAPI);
let connection = pluginAPI.getConnection();
connection.registerHandler(this.onPresenceVCardUpdate, 'vcard-temp:x:update', 'presence');
pluginAPI.addPublishAvatarProcessor(this.publishAvatarProcessor);
pluginAPI.addAvatarProcessor(this.avatarProcessor);
}
private getStorage() {
return this.pluginAPI.getStorage();
}
private onPresenceVCardUpdate = stanza => {
let from = new JID($(stanza).attr('from'));
let xVCard = $(stanza).find('x[xmlns="vcard-temp:x:update"]');
if (xVCard.length > 0) {
let photo = xVCard.find('photo');
if (photo.length > 0) {
let contact = this.pluginAPI.getContact(from);
if (contact && contact.getType() === ContactType.GROUPCHAT && from.resource) {
return true;
}
let sha1OfAvatar = photo.text()?.trim() || null;
this.getStorage().setItem(from.bare, sha1OfAvatar); //@REVIEW use this as trigger for all tabs?
if (!contact) {
this.pluginAPI.Log.warn('No contact found for', from);
return true;
}
let avatarUI = AvatarUI.get(contact);
avatarUI.reload();
}
}
return true;
};
private publishAvatarProcessor = (avatar: IAvatar | null): Promise<[IAvatar]> => {
if (typeof avatar === 'undefined') {
return Promise.resolve([avatar]);
}
const vcardService = this.pluginAPI.getConnection().getVcardService();
const jid = this.pluginAPI.getConnection().getJID();
return vcardService
.setAvatar(jid, avatar?.getData(), avatar?.getType())
.then(() => {
this.getStorage().setItem(jid.bare, avatar?.getHash() || '');
return [undefined];
})
.catch(err => {
this.pluginAPI.Log.error('Could not publish avatar', err);
return [avatar];
});
};
private avatarProcessor = async (contact: IContact, avatar: IAvatar): Promise<[IContact, IAvatar]> => {
let storage = this.getStorage();
let hash = storage.getItem(contact.getJid().bare);
if (!hash && !avatar && this.shouldForceRetrieval(contact)) {
try {
const avatarObject = await this.getAvatar(contact.getJid());
const data = avatarObject.src.replace(/^.+;base64,/, '');
avatar = new Avatar(Hash.SHA1FromBase64(data), avatarObject.type, avatarObject.src);
this.getStorage().setItem(contact.getJid().bare, avatar.getHash() || '');
let avatarUI = AvatarUI.get(contact);
avatarUI.reload();
} catch (err) {
// we could not find any avatar
}
}
if (!hash || avatar) {
return [contact, avatar];
}
try {
avatar = new Avatar(hash);
} catch (err) {
try {
const avatarObject = await this.getAvatar(contact.getJid());
return [contact, new Avatar(hash, avatarObject.type, avatarObject.src)];
} catch (err) {
this.pluginAPI.Log.warn('Error during avatar retrieval', err);
return [contact, avatar];
}
}
return [contact, avatar];
};
private getAvatar(jid: IJID): Promise<{ src: string; type: string }> {
let connection = this.pluginAPI.getConnection();
return connection
.getVcardService()
.loadVcard(jid)
.then(function (vcard) {
if (vcard.PHOTO && vcard.PHOTO.src) {
return vcard.PHOTO;
}
throw new Error('No photo available');
});
}
private shouldForceRetrieval(contact: IContact): boolean {
if (contact.getJid().bare !== this.pluginAPI.getConnection().getJID().bare) {
return false;
}
const sessionStorage = this.pluginAPI.getSessionStorage();
if (sessionStorage.getItem('forced', contact.getJid().bare)) {
return false;
}
sessionStorage.setItem('forced', contact.getJid().bare, true);
return true;
}
}