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 @@ ...@@ -29,6 +29,8 @@
### API changes ### API changes
- New API method `_converse.disco.supports` to check whether a certain - New API method `_converse.disco.supports` to check whether a certain
service discovery feature is supported by an entity. 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 ### UX/UI changes
- Use CSS3 fade transitions to render various elements. - Use CSS3 fade transitions to render various elements.
......
...@@ -829,6 +829,67 @@ To return an array of chat boxes, provide an array of JIDs: ...@@ -829,6 +829,67 @@ To return an array of chat boxes, provide an array of JIDs:
| url | The URL of the chat box heading. | | 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 The **rooms** grouping
---------------------- ----------------------
...@@ -1130,63 +1191,28 @@ Example: ...@@ -1130,63 +1191,28 @@ Example:
}); });
.. _`listen-grouping`: The **vcard** grouping
The **listen** grouping
----------------------- -----------------------
Converse.js emits events to which you can subscribe from your own JavaScript. get
~~~
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. Returns a Promise which results with the VCard data for a particular JID.
* ``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: Example:
.. code-block:: javascript .. code-block:: javascript
_converse.api.listen.once('message', function (messageXML) { ... }); converse.plugins.add('myplugin', {
initialize: function () {
* **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) { ... }); _converse.api.waitUntil('rosterContactsFetched').then(() => {
this._converse.api.vcard.get('someone@example.org').then(
(vcard) => {
// Do something with the vcard...
}
);
});
}
});
...@@ -1146,9 +1146,11 @@ ...@@ -1146,9 +1146,11 @@
var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'}); var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length;
}).then(function () {
expect(_converse.roster.pluck('jid').length).toBe(1); expect(_converse.roster.pluck('jid').length).toBe(1);
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
// Taken from the spec // Taken from the spec
// http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3 // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
stanza = $iq({ stanza = $iq({
...@@ -1175,6 +1177,7 @@ ...@@ -1175,6 +1177,7 @@
_converse.roster.onReceivedFromServer(stanza.tree()); _converse.roster.onReceivedFromServer(stanza.tree());
expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
done(); done();
});
})); }));
}); });
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
spyOn(_converse.roster, "addAndSubscribe").and.callThrough(); spyOn(_converse.roster, "addAndSubscribe").and.callThrough();
spyOn(_converse.roster, "addContact").and.callThrough(); spyOn(_converse.roster, "addContact").and.callThrough();
spyOn(_converse.roster, "sendContactAddIQ").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; var sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
...@@ -172,7 +172,7 @@ ...@@ -172,7 +172,7 @@
// A contact should now have been created // A contact should now have been created
expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy(); expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
expect(contact.get('jid')).toBe('contact@example.org'); 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, /* To subscribe to the contact's presence information,
* the user's client MUST send a presence stanza of * the user's client MUST send a presence stanza of
...@@ -525,9 +525,9 @@ ...@@ -525,9 +525,9 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
spyOn(_converse, "emit");
test_utils.openControlBox(_converse); test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
spyOn(_converse, "emit");
/* <presence /* <presence
* from='user@example.com' * from='user@example.com'
* to='contact@example.org' * to='contact@example.org'
...@@ -541,10 +541,10 @@ ...@@ -541,10 +541,10 @@
'xmlns': Strophe.NS.NICK, 'xmlns': Strophe.NS.NICK,
}).t('Clint Contact'); }).t('Clint Contact');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length; return $('a:contains("Contact requests")').length;
}).then(function () { }).then(function () {
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
var $header = $('a:contains("Contact requests")'); var $header = $('a:contains("Contact requests")');
expect($header.length).toBe(1); expect($header.length).toBe(1);
expect($header.is(":visible")).toBeTruthy(); expect($header.is(":visible")).toBeTruthy();
......
...@@ -10,7 +10,87 @@ ...@@ -10,7 +10,87 @@
define(["converse-core", "strophe.vcard"], factory); define(["converse-core", "strophe.vcard"], factory);
}(this, function (converse) { }(this, function (converse) {
"use strict"; "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', { converse.plugins.add('converse-vcard', {
...@@ -25,17 +105,15 @@ ...@@ -25,17 +105,15 @@
createRequestingContact (presence) { createRequestingContact (presence) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')); const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
_converse.getVCard(
bare_jid, _converse.api.vcard.get(bare_jid)
_.partial(_converse.createRequestingContactFromVCard, presence), .then(_.partial(_converse.createRequestingContactFromVCard, presence))
function (iq, jid) { .catch((vcard) => {
_converse.log( _converse.log(
`Error while retrieving vcard for ${jid}`, `Error while retrieving vcard for ${vcard.jid}`,
Strophe.LogLevel.WARN Strophe.LogLevel.WARN);
); _converse.createRequestingContactFromVCard(presence, vcard.stanza, vcard.jid);
_converse.createRequestingContactFromVCard(presence, iq, jid); });
}
);
} }
} }
}, },
...@@ -49,8 +127,9 @@ ...@@ -49,8 +127,9 @@
use_vcards: true, use_vcards: true,
}); });
_converse.createRequestingContactFromVCard = function (presence, iq, jid, fullname, img, img_type, url) { _converse.createRequestingContactFromVCard = function (presence, vcard) {
const bare_jid = Strophe.getBareJidFromJid(jid); const bare_jid = Strophe.getBareJidFromJid(vcard.jid);
let fullname = vcard.fullname;
if (!fullname) { if (!fullname) {
const nick_el = sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence); const nick_el = sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence);
fullname = nick_el.length ? nick_el[0].textContent : bare_jid; fullname = nick_el.length ? nick_el[0].textContent : bare_jid;
...@@ -61,68 +140,15 @@ ...@@ -61,68 +140,15 @@
ask: null, ask: null,
requesting: true, requesting: true,
fullname: fullname, fullname: fullname,
image: img, image: vcard.image,
image_type: img_type, image_type: vcard.image_type,
url, url: vcard.url,
vcard_updated: moment().format() vcard_updated: moment().format()
}; };
_converse.roster.create(user_data); _converse.roster.create(user_data);
_converse.emit('contactRequest', 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 */ /* Event handlers */
_converse.on('addClientFeatures', () => { _converse.on('addClientFeatures', () => {
if (_converse.use_vcards) { if (_converse.use_vcards) {
...@@ -138,47 +164,33 @@ ...@@ -138,47 +164,33 @@
const jid = chatbox.model.get('jid'), const jid = chatbox.model.get('jid'),
contact = _converse.roster.get(jid); contact = _converse.roster.get(jid);
if ((contact) && (!contact.get('vcard_updated'))) { if ((contact) && (!contact.get('vcard_updated'))) {
_converse.getVCard( updateChatBoxFromVCard(_converse, jid);
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
);
}
);
} }
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}; };
_converse.on('chatBoxInitialized', updateVCardForChatBox); _converse.on('chatBoxInitialized', updateVCardForChatBox);
const onContactAdd = function (contact) { _converse.on('initialized', () => {
_converse.roster.on("add", (contact) => {
if (!contact.get('vcard_updated')) { if (!contact.get('vcard_updated')) {
// This will update the vcard, which triggers a change _converse.api.vcard.get(contact.get('jid'));
// request which will rerender the roster contact.
_converse.getVCard(contact.get('jid'));
} }
}; });
_converse.on('initialized', function () {
_converse.roster.on("add", onContactAdd);
}); });
_converse.on('statusInitialized', function fetchOwnVCard () { _converse.on('statusInitialized', function fetchOwnVCard () {
if (_converse.xmppstatus.get('fullname') === undefined) { if (_converse.xmppstatus.get('fullname') === undefined) {
_converse.getVCard( _converse.api.vcard.get(_converse.bare_jid).then((vcard) => {
null, // No 'to' attr when getting one's own vCard _converse.xmppstatus.save({'fullname': vcard.fullname});
function (iq, jid, fullname) { });
_converse.xmppstatus.save({'fullname': 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