2018-05-05 20:28:41 +02:00
|
|
|
// Converse.js
|
2019-03-04 17:47:45 +01:00
|
|
|
// https://conversejs.org
|
2016-03-16 12:16:32 +01:00
|
|
|
//
|
2019-02-18 19:17:06 +01:00
|
|
|
// Copyright (c) 2013-2019, the Converse.js developers
|
2016-03-16 12:16:32 +01:00
|
|
|
// Licensed under the Mozilla Public License (MPLv2)
|
2019-07-11 10:48:52 +02:00
|
|
|
/**
|
|
|
|
* @module converse-vcard
|
|
|
|
*/
|
2019-05-23 14:26:20 +02:00
|
|
|
import BrowserStorage from "backbone.browserStorage";
|
2018-10-23 03:41:38 +02:00
|
|
|
import converse from "./converse-core";
|
|
|
|
import tpl_vcard from "./templates/vcard.html";
|
2017-12-02 18:00:18 +01:00
|
|
|
|
2019-08-23 14:03:52 +02:00
|
|
|
const { Backbone, Strophe, _, $iq, $build, dayjs, sizzle } = converse.env;
|
2018-10-23 03:41:38 +02:00
|
|
|
const u = converse.env.utils;
|
2016-03-16 12:16:32 +01:00
|
|
|
|
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
converse.plugins.add('converse-vcard', {
|
2018-05-15 09:47:52 +02:00
|
|
|
|
2019-09-04 19:11:26 +02:00
|
|
|
dependencies: ["converse-roster"],
|
|
|
|
|
|
|
|
overrides: {
|
|
|
|
XMPPStatus: {
|
|
|
|
getNickname () {
|
|
|
|
const { _converse } = this.__super__;
|
|
|
|
const nick = this.__super__.getNickname.apply(this);
|
|
|
|
if (!nick && _converse.xmppstatus.vcard) {
|
2019-09-05 12:49:10 +02:00
|
|
|
return _converse.xmppstatus.vcard.get('nickname');
|
2019-09-04 19:11:26 +02:00
|
|
|
} else {
|
|
|
|
return nick;
|
|
|
|
}
|
2019-09-05 12:49:10 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
getFullname (){
|
|
|
|
const { _converse } = this.__super__;
|
|
|
|
const fullname = this.__super__.getFullname.apply(this);
|
|
|
|
if (!fullname && _converse.xmppstatus.vcard) {
|
|
|
|
return _converse.xmppstatus.vcard.get('fullname');
|
|
|
|
} else {
|
|
|
|
return fullname;
|
|
|
|
}
|
2019-09-04 19:11:26 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
RosterContact: {
|
|
|
|
getDisplayName () {
|
|
|
|
if (!this.get('nickname') && this.vcard) {
|
|
|
|
return this.vcard.getDisplayName();
|
|
|
|
} else {
|
|
|
|
return this.__super__.getDisplayName.apply(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getFullname () {
|
|
|
|
if (this.vcard) {
|
|
|
|
return this.vcard.get('fullname');
|
|
|
|
} else {
|
|
|
|
return this.__super__.getFullname.apply(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
initialize () {
|
|
|
|
/* The initialize function gets called as soon as the plugin is
|
|
|
|
* loaded by converse.js's plugin machinery.
|
|
|
|
*/
|
|
|
|
const { _converse } = this;
|
2018-05-03 18:34:28 +02:00
|
|
|
|
2019-09-04 19:11:26 +02:00
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
_converse.VCard = Backbone.Model.extend({
|
|
|
|
defaults: {
|
|
|
|
'image': _converse.DEFAULT_IMAGE,
|
|
|
|
'image_type': _converse.DEFAULT_IMAGE_TYPE
|
|
|
|
},
|
2018-05-03 18:34:28 +02:00
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
set (key, val, options) {
|
|
|
|
// Override Backbone.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;
|
2018-05-08 17:48:37 +02:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
if (_.has(attrs, 'image') && !attrs['image']) {
|
|
|
|
attrs['image'] = _converse.DEFAULT_IMAGE;
|
|
|
|
attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE;
|
|
|
|
return Backbone.Model.prototype.set.call(this, attrs, options);
|
2018-08-27 21:54:18 +02:00
|
|
|
} else {
|
2018-10-23 03:41:38 +02:00
|
|
|
return Backbone.Model.prototype.set.apply(this, arguments);
|
2018-05-08 17:48:37 +02:00
|
|
|
}
|
2019-04-09 20:34:23 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
getDisplayName () {
|
|
|
|
return this.get('nickname') || this.get('fullname') || this.get('jid');
|
2018-05-08 17:48:37 +02:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
});
|
2018-05-08 17:48:37 +02:00
|
|
|
|
|
|
|
|
2019-08-01 10:26:35 +02:00
|
|
|
_converse.VCards = _converse.Collection.extend({
|
2018-10-23 03:41:38 +02:00
|
|
|
model: _converse.VCard,
|
2018-05-08 17:48:37 +02:00
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
initialize () {
|
2018-05-29 12:00:23 +02:00
|
|
|
this.on('add', vcard => _converse.api.vcard.update(vcard));
|
2018-10-23 03:41:38 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2018-10-25 07:32:44 +02:00
|
|
|
async function onVCardData (jid, iq) {
|
2018-10-23 03:41:38 +02:00
|
|
|
const vcard = iq.querySelector('vCard');
|
|
|
|
let result = {};
|
2019-08-05 01:39:57 +02:00
|
|
|
if (vcard !== null) {
|
2018-10-23 03:41:38 +02:00
|
|
|
result = {
|
|
|
|
'stanza': iq,
|
|
|
|
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
|
|
|
|
'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'),
|
|
|
|
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
|
|
|
|
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
|
|
|
|
'url': _.get(vcard.querySelector('URL'), 'textContent'),
|
|
|
|
'role': _.get(vcard.querySelector('ROLE'), 'textContent'),
|
|
|
|
'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent'),
|
2019-05-05 19:05:45 +02:00
|
|
|
'vcard_updated': (new Date()).toISOString(),
|
2018-10-23 03:41:38 +02:00
|
|
|
'vcard_error': undefined
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (result.image) {
|
|
|
|
const buffer = u.base64ToArrayBuffer(result['image']);
|
2018-10-25 07:32:44 +02:00
|
|
|
const ab = await crypto.subtle.digest('SHA-1', buffer);
|
|
|
|
result['image_hash'] = u.arrayBufferToHex(ab);
|
2018-05-08 17:48:37 +02:00
|
|
|
}
|
2018-10-25 07:32:44 +02:00
|
|
|
return result;
|
2018-10-23 03:41:38 +02:00
|
|
|
}
|
2018-05-08 17:48:37 +02:00
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
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);
|
2018-05-03 18:34:28 +02:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
return iq;
|
|
|
|
}
|
2018-05-03 18:34:28 +02:00
|
|
|
|
2018-10-25 07:32:44 +02:00
|
|
|
async function getVCard (_converse, jid) {
|
2018-10-23 03:41:38 +02:00
|
|
|
const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
|
2018-10-25 07:32:44 +02:00
|
|
|
let iq;
|
|
|
|
try {
|
|
|
|
iq = await _converse.api.sendIQ(createStanza("get", to))
|
|
|
|
} catch (iq) {
|
|
|
|
return {
|
|
|
|
'stanza': iq,
|
|
|
|
'jid': jid,
|
2019-05-05 19:05:45 +02:00
|
|
|
'vcard_error': (new Date()).toISOString()
|
2018-10-25 07:32:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return onVCardData(jid, iq);
|
2018-10-23 03:41:38 +02:00
|
|
|
}
|
|
|
|
|
2019-04-29 09:07:15 +02:00
|
|
|
/************************ BEGIN Event Handlers ************************/
|
2018-10-23 03:41:38 +02:00
|
|
|
_converse.initVCardCollection = function () {
|
|
|
|
_converse.vcards = new _converse.VCards();
|
2019-02-18 19:06:11 +01:00
|
|
|
const id = `${_converse.bare_jid}-converse.vcards`;
|
2019-05-23 14:26:20 +02:00
|
|
|
_converse.vcards.browserStorage = new BrowserStorage[_converse.config.get('storage')](id);
|
2018-10-23 03:41:38 +02:00
|
|
|
_converse.vcards.fetch();
|
|
|
|
}
|
2018-05-29 12:00:23 +02:00
|
|
|
_converse.api.listen.on('afterResourceBinding', _converse.initVCardCollection);
|
2017-07-21 12:41:16 +02:00
|
|
|
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2019-04-09 20:34:23 +02:00
|
|
|
_converse.api.listen.on('statusInitialized', () => {
|
|
|
|
const vcards = _converse.vcards;
|
2019-07-01 10:44:59 +02:00
|
|
|
if (_converse.session) {
|
|
|
|
const jid = _converse.session.get('bare_jid');
|
|
|
|
_converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
|
|
|
|
}
|
2019-04-09 20:34:23 +02:00
|
|
|
});
|
|
|
|
|
2019-08-01 10:26:35 +02:00
|
|
|
_converse.api.listen.on('clearSession', () => {
|
|
|
|
if (_converse.shouldClearCache() && _converse.vcards) {
|
|
|
|
_converse.vcards.clearSession();
|
|
|
|
delete _converse.vcards;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-04-09 20:34:23 +02:00
|
|
|
|
2018-05-29 12:00:23 +02:00
|
|
|
_converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.VCARD));
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2019-09-04 19:11:26 +02:00
|
|
|
|
|
|
|
function setVCardOnModel (model) {
|
|
|
|
// TODO: if we can make this method async and wait for the VCard to
|
|
|
|
// be updated, then we'll avoid unnecessary re-rendering of roster contacts.
|
|
|
|
const jid = model.get('jid');
|
|
|
|
model.vcard = _converse.vcards.findWhere({'jid': jid});
|
|
|
|
if (!model.vcard) {
|
|
|
|
model.vcard = _converse.vcards.create({'jid': jid});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_converse.api.listen.on('rosterContactInitialized', contact => setVCardOnModel(contact));
|
|
|
|
|
|
|
|
|
2019-04-29 09:07:15 +02:00
|
|
|
/************************ BEGIN API ************************/
|
|
|
|
Object.assign(_converse.api, {
|
2018-10-23 03:41:38 +02:00
|
|
|
/**
|
|
|
|
* The XEP-0054 VCard API
|
|
|
|
*
|
|
|
|
* This API lets you access and update user VCards
|
|
|
|
*
|
|
|
|
* @namespace _converse.api.vcard
|
|
|
|
* @memberOf _converse.api
|
|
|
|
*/
|
|
|
|
'vcard': {
|
2018-09-02 15:07:14 +02:00
|
|
|
/**
|
2018-10-23 03:41:38 +02:00
|
|
|
* Enables setting new values for a VCard.
|
2018-09-02 15:07:14 +02:00
|
|
|
*
|
2018-10-23 03:41:38 +02:00
|
|
|
* @method _converse.api.vcard.set
|
|
|
|
* @param {string} jid The JID for which the VCard should be set
|
|
|
|
* @param {object} data A map of VCard keys and values
|
|
|
|
* @example
|
|
|
|
* _converse.api.vcard.set({
|
|
|
|
* 'jid': _converse.bare_jid,
|
|
|
|
* 'fn': 'John Doe',
|
|
|
|
* 'nickname': 'jdoe'
|
|
|
|
* }).then(() => {
|
|
|
|
* // Succes
|
|
|
|
* }).catch(() => {
|
|
|
|
* // Failure
|
|
|
|
* }).
|
|
|
|
*/
|
2019-07-01 10:44:59 +02:00
|
|
|
set (jid, data) {
|
2019-09-04 19:11:26 +02:00
|
|
|
if (!jid) {
|
|
|
|
throw Error("No jid provided for the VCard data");
|
|
|
|
}
|
|
|
|
const vcard_el = Strophe.xmlHtmlNode(tpl_vcard(data)).firstElementChild;
|
|
|
|
return _converse.api.sendIQ(createStanza("set", jid, vcard_el));
|
2018-10-23 03:41:38 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @method _converse.api.vcard.get
|
|
|
|
* @param {Backbone.Model|string} model Either a `Backbone.Model` instance, or a string JID.
|
|
|
|
* If a `Backbone.Model` instance is passed in, then it must have either a `jid`
|
|
|
|
* attribute or a `muc_jid` attribute.
|
|
|
|
* @param {boolean} [force] A boolean indicating whether the vcard should be
|
|
|
|
* fetched even if it's been fetched before.
|
|
|
|
* @returns {promise} A Promise which resolves with the VCard data for a particular JID or for
|
|
|
|
* a `Backbone.Model` instance which represents an entity with a JID (such as a roster contact,
|
|
|
|
* chat or chatroom occupant).
|
2018-09-02 15:07:14 +02:00
|
|
|
*
|
2018-10-23 03:41:38 +02:00
|
|
|
* @example
|
|
|
|
* _converse.api.waitUntil('rosterContactsFetched').then(() => {
|
|
|
|
* _converse.api.vcard.get('someone@example.org').then(
|
|
|
|
* (vcard) => {
|
|
|
|
* // Do something with the vcard...
|
|
|
|
* }
|
|
|
|
* );
|
|
|
|
* });
|
2018-09-02 15:07:14 +02:00
|
|
|
*/
|
2018-05-29 12:00:23 +02:00
|
|
|
get (model, force) {
|
2018-10-23 03:41:38 +02:00
|
|
|
if (_.isString(model)) {
|
|
|
|
return getVCard(_converse, model);
|
|
|
|
} else if (force ||
|
|
|
|
!model.get('vcard_updated') ||
|
2019-05-06 11:16:56 +02:00
|
|
|
!dayjs(model.get('vcard_error')).isSame(new Date(), "day")) {
|
2018-10-23 03:41:38 +02:00
|
|
|
|
|
|
|
const jid = model.get('jid');
|
|
|
|
if (!jid) {
|
|
|
|
throw new Error("No JID to get vcard for!");
|
2018-05-01 11:33:32 +02:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
return getVCard(_converse, jid);
|
|
|
|
} else {
|
|
|
|
return Promise.resolve({});
|
2017-12-02 18:00:18 +01:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches the VCard associated with a particular `Backbone.Model` instance
|
|
|
|
* (by using its `jid` or `muc_jid` attribute) and then updates the model with the
|
|
|
|
* returned VCard data.
|
|
|
|
*
|
|
|
|
* @method _converse.api.vcard.update
|
|
|
|
* @param {Backbone.Model} model A `Backbone.Model` instance
|
|
|
|
* @param {boolean} [force] A boolean indicating whether the vcard should be
|
|
|
|
* fetched again even if it's been fetched before.
|
|
|
|
* @returns {promise} A promise which resolves once the update has completed.
|
|
|
|
* @example
|
|
|
|
* _converse.api.waitUntil('rosterContactsFetched').then(() => {
|
|
|
|
* const chatbox = _converse.chatboxes.getChatBox('someone@example.org');
|
|
|
|
* _converse.api.vcard.update(chatbox);
|
|
|
|
* });
|
|
|
|
*/
|
2019-09-04 19:11:26 +02:00
|
|
|
async update (model, force) {
|
|
|
|
const vcard = await this.get(model, force);
|
|
|
|
delete vcard['stanza']
|
|
|
|
model.save(vcard);
|
2016-03-16 12:16:32 +01:00
|
|
|
}
|
2018-10-23 03:41:38 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|