Commit 8662f751 authored by JC Brand's avatar JC Brand

Refactor `converse-vcard` and add API method to fetch a VCard

parent 063908e0
......@@ -29,6 +29,8 @@
### API changes
- New API method `_converse.disco.supports` to check whether a certain
service discovery feature is supported by an entity.
- New API method `_converse.api.vcard.get` which fetches the VCard for a
particular JID.
### UX/UI changes
- Use CSS3 fade transitions to render various elements.
......
......@@ -829,6 +829,67 @@ To return an array of chat boxes, provide an array of JIDs:
| url | The URL of the chat box heading. |
+-------------+-----------------------------------------------------+
.. _`listen-grouping`:
The **listen** grouping
-----------------------
Converse.js emits events to which you can subscribe from your own JavaScript.
Concerning events, the following methods are available under the "listen"
grouping:
* **on(eventName, callback, [context])**:
Calling the ``on`` method allows you to subscribe to an event.
Every time the event fires, the callback method specified by ``callback`` will be
called.
Parameters:
* ``eventName`` is the event name as a string.
* ``callback`` is the callback method to be called when the event is emitted.
* ``context`` (optional), the value of the `this` parameter for the callback.
For example:
.. code-block:: javascript
_converse.api.listen.on('message', function (messageXML) { ... });
* **once(eventName, callback, [context])**:
Calling the ``once`` method allows you to listen to an event
exactly once.
Parameters:
* ``eventName`` is the event name as a string.
* ``callback`` is the callback method to be called when the event is emitted.
* ``context`` (optional), the value of the `this` parameter for the callback.
For example:
.. code-block:: javascript
_converse.api.listen.once('message', function (messageXML) { ... });
* **not(eventName, callback)**
To stop listening to an event, you can use the ``not`` method.
Parameters:
* ``eventName`` is the event name as a string.
* ``callback`` refers to the function that is to be no longer executed.
For example:
.. code-block:: javascript
_converse.api.listen.not('message', function (messageXML) { ... });
The **rooms** grouping
----------------------
......@@ -1130,63 +1191,28 @@ Example:
});
.. _`listen-grouping`:
The **listen** grouping
The **vcard** grouping
-----------------------
Converse.js emits events to which you can subscribe from your own JavaScript.
Concerning events, the following methods are available under the "listen"
grouping:
* **on(eventName, callback, [context])**:
Calling the ``on`` method allows you to subscribe to an event.
Every time the event fires, the callback method specified by ``callback`` will be
called.
Parameters:
* ``eventName`` is the event name as a string.
* ``callback`` is the callback method to be called when the event is emitted.
* ``context`` (optional), the value of the `this` parameter for the callback.
For example:
.. code-block:: javascript
_converse.api.listen.on('message', function (messageXML) { ... });
* **once(eventName, callback, [context])**:
Calling the ``once`` method allows you to listen to an event
exactly once.
Parameters:
get
~~~
* ``eventName`` is the event name as a string.
* ``callback`` is the callback method to be called when the event is emitted.
* ``context`` (optional), the value of the `this` parameter for the callback.
Returns a Promise which results with the VCard data for a particular JID.
For example:
Example:
.. code-block:: javascript
_converse.api.listen.once('message', function (messageXML) { ... });
* **not(eventName, callback)**
To stop listening to an event, you can use the ``not`` method.
Parameters:
* ``eventName`` is the event name as a string.
* ``callback`` refers to the function that is to be no longer executed.
For example:
.. code-block:: javascript
converse.plugins.add('myplugin', {
initialize: function () {
_converse.api.listen.not('message', function (messageXML) { ... });
_converse.api.waitUntil('rosterContactsFetched').then(() => {
this._converse.api.vcard.get('someone@example.org').then(
(vcard) => {
// Do something with the vcard...
}
);
});
}
});
......@@ -1146,35 +1146,38 @@
var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.pluck('jid').length).toBe(1);
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
// Taken from the spec
// http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
stanza = $iq({
to: _converse.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster',
}).c('item', {
jid: 'romeo@example.net',
name: 'Romeo',
subscription:'both'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'mercutio@example.org',
name: 'Mercutio',
subscription:'from'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'benvolio@example.org',
name: 'Benvolio',
subscription:'both'
}).c('group').t('Friends');
_converse.roster.onReceivedFromServer(stanza.tree());
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
done();
test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length;
}).then(function () {
expect(_converse.roster.pluck('jid').length).toBe(1);
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
// Taken from the spec
// http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
stanza = $iq({
to: _converse.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster',
}).c('item', {
jid: 'romeo@example.net',
name: 'Romeo',
subscription:'both'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'mercutio@example.org',
name: 'Mercutio',
subscription:'from'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'benvolio@example.org',
name: 'Benvolio',
subscription:'both'
}).c('group').t('Friends');
_converse.roster.onReceivedFromServer(stanza.tree());
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
done();
});
}));
});
......
......@@ -64,7 +64,7 @@
spyOn(_converse.roster, "addAndSubscribe").and.callThrough();
spyOn(_converse.roster, "addContact").and.callThrough();
spyOn(_converse.roster, "sendContactAddIQ").and.callThrough();
spyOn(_converse, "getVCard").and.callThrough();
spyOn(_converse.api.vcard, "get").and.callThrough();
var sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq;
......@@ -172,7 +172,7 @@
// A contact should now have been created
expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
expect(contact.get('jid')).toBe('contact@example.org');
expect(_converse.getVCard).toHaveBeenCalled();
expect(_converse.api.vcard.get).toHaveBeenCalled();
/* To subscribe to the contact's presence information,
* the user's client MUST send a presence stanza of
......@@ -525,9 +525,9 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
spyOn(_converse, "emit");
test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
spyOn(_converse, "emit");
/* <presence
* from='user@example.com'
* to='contact@example.org'
......@@ -541,10 +541,10 @@
'xmlns': Strophe.NS.NICK,
}).t('Clint Contact');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length;
}).then(function () {
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
var $header = $('a:contains("Contact requests")');
expect($header.length).toBe(1);
expect($header.is(":visible")).toBeTruthy();
......
......@@ -10,7 +10,87 @@
define(["converse-core", "strophe.vcard"], factory);
}(this, function (converse) {
"use strict";
const { Strophe, _, moment, sizzle } = converse.env;
const { Promise, Strophe, _, moment, sizzle } = converse.env;
function onVCardData (_converse, jid, iq, callback) {
const vcard = iq.querySelector('vCard'),
img_type = _.get(vcard.querySelector('TYPE'), 'textContent'),
img = _.get(vcard.querySelector('BINVAL'), 'textContent'),
url = _.get(vcard.querySelector('URL'), 'textContent'),
fullname = _.get(vcard.querySelector('FN'), 'textContent');
if (jid) {
const contact = _converse.roster.get(jid);
if (contact) {
contact.save({
'fullname': fullname || _.get(contact, 'fullname', jid),
'image_type': img_type,
'image': img,
'url': url,
'vcard_updated': moment().format()
});
}
}
if (callback) {
callback({
'stanza': iq,
'jid': jid,
'fullname': fullname || jid,
'image': img,
'image_type': img_type,
'url': url
});
}
}
function onVCardError (_converse, jid, iq, errback) {
const contact = _converse.roster.get(jid);
if (contact) {
contact.save({'vcard_updated': moment().format() });
}
if (errback) { errback({'stanza': iq, 'jid': jid}); }
}
function getVCard (_converse, jid) {
/* Request the VCard of another user. Returns a promise.
*
* Parameters:
* (String) jid - The Jabber ID of the user whose VCard
* is being requested.
*/
if (Strophe.getBareJidFromJid(jid) === _converse.bare_jid) {
jid = null; // No 'to' attr when getting one's own vCard
}
return new Promise((resolve, reject) => {
if (!_converse.use_vcards) {
if (resolve) { resolve({'jid': jid}); }
} else {
_converse.connection.vcard.get(
_.partial(onVCardData, _converse, jid, _, resolve),
jid,
_.partial(onVCardError, _converse, jid, _, resolve)
);
}
});
}
function updateChatBoxFromVCard (_converse, jid) {
_converse.api.vcard.get(jid)
.then((vcard) => {
const chatbox = _converse.chatboxes.getChatBox(vcard.jid);
if (!_.isUndefined(chatbox)) {
chatbox.save(_.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'vcard_updated']));
}
})
.catch(() => {
_converse.log(
"updateChatBoxFromVCard: Error occured while attempting to update chatbox with VCard data",
Strophe.LogLevel.ERROR
);
});
}
converse.plugins.add('converse-vcard', {
......@@ -25,17 +105,15 @@
createRequestingContact (presence) {
const { _converse } = this.__super__;
const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
_converse.getVCard(
bare_jid,
_.partial(_converse.createRequestingContactFromVCard, presence),
function (iq, jid) {
_converse.api.vcard.get(bare_jid)
.then(_.partial(_converse.createRequestingContactFromVCard, presence))
.catch((vcard) => {
_converse.log(
`Error while retrieving vcard for ${jid}`,
Strophe.LogLevel.WARN
);
_converse.createRequestingContactFromVCard(presence, iq, jid);
}
);
`Error while retrieving vcard for ${vcard.jid}`,
Strophe.LogLevel.WARN);
_converse.createRequestingContactFromVCard(presence, vcard.stanza, vcard.jid);
});
}
}
},
......@@ -49,8 +127,9 @@
use_vcards: true,
});
_converse.createRequestingContactFromVCard = function (presence, iq, jid, fullname, img, img_type, url) {
const bare_jid = Strophe.getBareJidFromJid(jid);
_converse.createRequestingContactFromVCard = function (presence, vcard) {
const bare_jid = Strophe.getBareJidFromJid(vcard.jid);
let fullname = vcard.fullname;
if (!fullname) {
const nick_el = sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence);
fullname = nick_el.length ? nick_el[0].textContent : bare_jid;
......@@ -61,68 +140,15 @@
ask: null,
requesting: true,
fullname: fullname,
image: img,
image_type: img_type,
url,
image: vcard.image,
image_type: vcard.image_type,
url: vcard.url,
vcard_updated: moment().format()
};
_converse.roster.create(user_data);
_converse.emit('contactRequest', user_data);
};
_converse.onVCardError = function (jid, iq, errback) {
const contact = _.get(_converse.roster, jid);
if (contact) {
contact.save({ 'vcard_updated': moment().format() });
}
if (errback) { errback(iq, jid); }
};
_converse.onVCardData = function (jid, iq, callback) {
const vcard = iq.querySelector('vCard'),
img_type = _.get(vcard.querySelector('TYPE'), 'textContent'),
img = _.get(vcard.querySelector('BINVAL'), 'textContent'),
url = _.get(vcard.querySelector('URL'), 'textContent'),
fullname = _.get(vcard.querySelector('FN'), 'textContent');
if (jid) {
const contact = _converse.roster.get(jid);
if (contact) {
contact.save({
'fullname': fullname || _.get(contact, 'fullname', jid),
'image_type': img_type,
'image': img,
'url': url,
'vcard_updated': moment().format()
});
}
}
if (callback) {
callback(iq, jid, fullname, img, img_type, url);
}
};
_converse.getVCard = function (jid, callback, errback) {
/* Request the VCard of another user.
*
* Parameters:
* (String) jid - The Jabber ID of the user whose VCard
* is being requested.
* (Function) callback - A function to call once the VCard is
* returned.
* (Function) errback - A function to call if an error occured
* while trying to fetch the VCard.
*/
if (!_converse.use_vcards) {
if (callback) { callback(null, jid); }
} else {
_converse.connection.vcard.get(
_.partial(_converse.onVCardData, jid, _, callback),
jid,
_.partial(_converse.onVCardError, jid, _, errback));
}
};
/* Event handlers */
_converse.on('addClientFeatures', () => {
if (_converse.use_vcards) {
......@@ -138,47 +164,33 @@
const jid = chatbox.model.get('jid'),
contact = _converse.roster.get(jid);
if ((contact) && (!contact.get('vcard_updated'))) {
_converse.getVCard(
jid,
function (iq, jid, fullname, image, image_type, url) {
chatbox.model.save({
'fullname' : fullname || jid,
'url': url,
'image_type': image_type,
'image': image
});
},
function () {
_converse.log(
"updateVCardForChatBox: Error occured while fetching vcard",
Strophe.LogLevel.ERROR
);
}
);
updateChatBoxFromVCard(_converse, jid);
}
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
};
_converse.on('chatBoxInitialized', updateVCardForChatBox);
const onContactAdd = function (contact) {
if (!contact.get('vcard_updated')) {
// This will update the vcard, which triggers a change
// request which will rerender the roster contact.
_converse.getVCard(contact.get('jid'));
}
};
_converse.on('initialized', function () {
_converse.roster.on("add", onContactAdd);
_converse.on('initialized', () => {
_converse.roster.on("add", (contact) => {
if (!contact.get('vcard_updated')) {
_converse.api.vcard.get(contact.get('jid'));
}
});
});
_converse.on('statusInitialized', function fetchOwnVCard () {
if (_converse.xmppstatus.get('fullname') === undefined) {
_converse.getVCard(
null, // No 'to' attr when getting one's own vCard
function (iq, jid, fullname) {
_converse.xmppstatus.save({'fullname': fullname});
}
);
_converse.api.vcard.get(_converse.bare_jid).then((vcard) => {
_converse.xmppstatus.save({'fullname': vcard.fullname});
});
}
});
_.extend(_converse.api, {
'vcard': {
'get' (jid) {
return getVCard(_converse, jid);
}
}
});
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment