Move VCard functionality into separate plugin

This commit is contained in:
JC Brand 2016-03-16 11:16:32 +00:00
parent 5651f76373
commit 08222182c5
7 changed files with 261 additions and 149 deletions

View File

@ -58,6 +58,7 @@ require.config({
"converse-register": "src/converse-register",
"converse-rosterview": "src/converse-rosterview",
"converse-templates": "src/converse-templates",
"converse-vcard": "src/converse-vcard",
// Off-the-record-encryption
"bigint": "src/bigint",
@ -230,8 +231,9 @@ if (typeof define !== 'undefined') {
// translations that you care about.
"converse-chatview", // Renders standalone chat boxes for single user chat
"converse-mam",
"converse-mam", // XEP-0313 Message Archive Management
"converse-muc", // XEP-0045 Multi-user chat
"converse-vcard", // XEP-0054 VCard-temp
"converse-otr", // Off-the-record encryption for one-on-one messages
"converse-controlbox", // The control box
"converse-register", // XEP-0077 In-band registration

View File

@ -758,21 +758,23 @@ Here are the different events that are emitted:
+=================================+===================================================================================================+======================================================================================================+
| **callButtonClicked** | When a call button (i.e. with class .toggle-call) on a chat box has been clicked. | ``converse.listen.on('callButtonClicked', function (event, connection, model) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **chatBoxOpened** | When a chat box has been opened. | ``converse.listen.on('chatBoxOpened', function (event, chatbox) { ... });`` |
| **chatBoxInitialized** | When a chat box has been initialized. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatBoxInitialized', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **chatRoomOpened** | When a chat room has been opened. | ``converse.listen.on('chatRoomOpened', function (event, chatbox) { ... });`` |
| **chatBoxOpened** | When a chat box has been opened. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatBoxOpened', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **chatBoxClosed** | When a chat box has been closed. | ``converse.listen.on('chatBoxClosed', function (event, chatbox) { ... });`` |
| **chatRoomOpened** | When a chat room has been opened. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatRoomOpened', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **chatBoxFocused** | When the focus has been moved to a chat box. | ``converse.listen.on('chatBoxFocused', function (event, chatbox) { ... });`` |
| **chatBoxClosed** | When a chat box has been closed. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatBoxClosed', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **chatBoxToggled** | When a chat box has been minimized or maximized. | ``converse.listen.on('chatBoxToggled', function (event, chatbox) { ... });`` |
| **chatBoxFocused** | When the focus has been moved to a chat box. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatBoxFocused', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **contactRequest** | Someone has requested to subscribe to your presence (i.e. to be your contact). | ``converse.listen.on('contactRequest', function (event, user_data) { ... });`` |
| **chatBoxToggled** | When a chat box has been minimized or maximized. Relevant to converse-chatview.js plugin. | ``converse.listen.on('chatBoxToggled', function (event, chatbox) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **contactStatusChanged** | When a chat buddy's chat status has changed. | ``converse.listen.on('contactStatusChanged', function (event, buddy) { ... });`` |
| **contactRequest** | Someone has requested to subscribe to your presence (i.e. to be your contact). | ``converse.listen.on('contactRequest', function (event, user_data) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **contactStatusMessageChanged** | When a chat buddy's custom status message has changed. | ``converse.listen.on('contactStatusMessageChanged', function (event, data) { ... });`` |
| **contactStatusChanged** | When a chat buddy's chat status has changed. | ``converse.listen.on('contactStatusChanged', function (event, buddy) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **contactStatusMessageChanged** | When a chat buddy's custom status message has changed. | ``converse.listen.on('contactStatusMessageChanged', function (event, data) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **message** | When a message is received. | ``converse.listen.on('message', function (event, messageXML) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
@ -782,18 +784,20 @@ Here are the different events that are emitted:
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **initialized** | Once converse.js has been initialized. | ``converse.listen.on('initialized', function (event) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **ready** | After connection has been established and converse.js has got all its ducks in a row. | ``converse.listen.on('ready', function (event) { ... });`` |
| **connected** | After connection has been established and converse.js has got all its ducks in a row. | ``converse.listen.on('connected', function (event) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **reconnect** | After the connection has dropped. Converse.js will attempt to reconnect when not in prebind mode. | ``converse.listen.on('reconnect', function (event) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **roomInviteSent** | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.listen.on('roomInvite', function (event, data) { ... });`` |
| **roomInviteSent** | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.listen.on('roomInvite', function (event, data) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **roomInviteReceived** | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.listen.on('roomInvite', function (event, data) { ... });`` |
| **roomInviteReceived** | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.listen.on('roomInvite', function (event, data) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **roster** | When the roster is updated. | ``converse.listen.on('roster', function (event, items) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **rosterPush** | When the roster receives a push event from server. (i.e. New entry in your buddy list) | ``converse.listen.on('rosterPush', function (event, items) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **statusInitialized** | When own chat status has been initialized. | ``converse.listen.on('statusInitialized', function (event, status) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **statusChanged** | When own chat status has changed. | ``converse.listen.on('statusChanged', function (event, status) { ... });`` |
+---------------------------------+---------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| **statusMessageChanged** | When own custom status message has changed. | ``converse.listen.on('statusMessageChanged', function (event, message) { ... });`` |

View File

@ -10,6 +10,7 @@
);
} (this, function ($, mock, test_utils) {
"use strict";
var Strophe = converse_api.env.Strophe;
var $iq = converse_api.env.$iq;
var $pres = converse_api.env.$pres;
// See:
@ -173,7 +174,9 @@
*/
expect(contact.subscribe).toHaveBeenCalled();
expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
"<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'/>"
"<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'>"+
"<nick xmlns='http://jabber.org/protocol/nick'>Max Mustermann</nick>"+
"</presence>"
);
/* As a result, the user's server MUST initiate a second roster
* push to all of the user's available resources that have
@ -503,16 +506,18 @@
runs(function () {
spyOn(converse, "emit");
/*
* <presence
* from='user@example.com'
* to='contact@example.org'
* type='subscribe'/>
*/
* <presence
* from='user@example.com'
* to='contact@example.org'
* type='subscribe'/>
*/
var stanza = $pres({
'to': converse.bare_jid,
'from': 'contact@example.org',
'type': 'subscribe'
});
}).c('nick', {
'xmlns': Strophe.NS.NICK,
}).t('Clint Contact');
this.connection._dataRecv(test_utils.createRequest(stanza));
expect(converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
var $header = $('a:contains("Contact requests")');

View File

@ -91,7 +91,8 @@
this.model.on('change:status', this.onStatusChanged, this);
this.model.on('showHelpMessages', this.showHelpMessages, this);
this.model.on('sendMessage', this.sendMessage, this);
this.updateVCard().render().fetchMessages().insertIntoPage().hide();
this.render().fetchMessages().insertIntoPage().hide();
converse.emit('chatBoxInitialized', this);
},
render: function () {
@ -802,29 +803,6 @@
this.$el.hide('fast', this.onMinimized.bind(this));
},
updateVCard: function () {
if (!this.use_vcards) { return this; }
var jid = this.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) {
this.model.save({
'fullname' : fullname || jid,
'url': url,
'image_type': image_type,
'image': image
});
}.bind(this),
function () {
converse.log("ChatBoxView.initialize: An error occured while fetching vcard");
}
);
}
return this;
},
renderToolbar: function (options) {
if (!converse.show_toolbar) {
return;

View File

@ -24,7 +24,6 @@
"strophe",
"converse-templates",
"strophe.disco",
"strophe.vcard",
"backbone.browserStorage",
"backbone.overview",
], factory);
@ -148,6 +147,7 @@
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
Strophe.addNamespace('XFORM', 'jabber:x:data');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
// Instance level constants
this.TIMEOUTS = { // Set as module attr so that we can override in tests.
@ -278,7 +278,6 @@
storage: 'session',
strict_plugin_dependencies: false,
synchronize_availability: true, // Set to false to not sync with other clients or with resource name of the particular client that it should synchronize with
use_vcards: true,
visible_toolbar_buttons: {
'emoticons': true,
'call': false,
@ -427,51 +426,6 @@
converse.connection.send(pres);
};
this.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 (!this.use_vcards) {
if (callback) { callback(jid, jid); }
return;
}
converse.connection.vcard.get(
function (iq) { // Successful callback
var $vcard = $(iq).find('vCard');
var fullname = $vcard.find('FN').text(),
img = $vcard.find('BINVAL').text(),
img_type = $vcard.find('TYPE').text(),
url = $vcard.find('URL').text();
if (jid) {
var contact = converse.roster.get(jid);
if (contact) {
fullname = _.isEmpty(fullname)? contact.get('fullname') || jid: fullname;
contact.save({
'fullname': fullname,
'image_type': img_type,
'image': img,
'url': url,
'vcard_updated': moment().format()
});
}
}
if (callback) { callback(iq, jid, fullname, img, img_type, url); }
}.bind(this),
jid,
function (iq) { // Error callback
var contact = converse.roster.get(jid);
if (contact) {
contact.save({ 'vcard_updated': moment().format() });
}
if (errback) { errback(iq, jid); }
}
);
};
this.reconnect = function (condition) {
converse.log('Attempting to reconnect in 5 seconds');
@ -591,12 +545,18 @@
this.updateMsgCounter();
};
this.initStatus = function (callback) {
this.initStatus = function () {
var deferred = new $.Deferred();
this.xmppstatus = new this.XMPPStatus();
var id = b64_sha1('converse.xmppstatus-'+converse.bare_jid);
this.xmppstatus.id = id; // Appears to be necessary for backbone.browserStorage
this.xmppstatus.browserStorage = new Backbone.BrowserStorage[converse.storage](id);
this.xmppstatus.fetch({success: callback, error: callback});
this.xmppstatus.fetch({
success: deferred.resolve,
error: deferred.resolve
});
converse.emit('statusInitialized');
return deferred.promise();
};
this.initSession = function () {
@ -667,7 +627,7 @@
// We need to re-register all the event handlers on the newly
// created connection.
var deferred = new $.Deferred();
this.initStatus(function () {
this.initStatus().done(function () {
this.afterReconnected();
deferred.resolve();
}.bind(this));
@ -698,6 +658,31 @@
this.connection.send(carbons_iq);
};
this.onStatusInitialized = function (deferred) {
this.registerIntervalHandler();
this.roster = new this.RosterContacts();
this.roster.browserStorage = new Backbone.BrowserStorage[this.storage](
b64_sha1('converse.contacts-'+this.bare_jid));
this.chatboxes.onConnected();
this.giveFeedback(__('Contacts'));
if (typeof this.callback === 'function') {
// A callback method may be passed in via the
// converse.initialize method.
// XXX: Can we use $.Deferred instead of this callback?
if (this.connection.service === 'jasmine tests') {
// XXX: Call back with the internal converse object. This
// object should never be exposed to production systems.
// 'jasmine tests' is an invalid http bind service value,
// so we're sure that this is just for tests.
this.callback(this);
} else {
this.callback();
}
}
deferred.resolve();
};
this.onConnected = function (callback) {
// When reconnecting, there might be some open chat boxes. We don't
// know whether these boxes are of the same account or not, so we
@ -710,30 +695,9 @@
this.domain = Strophe.getDomainFromJid(this.connection.jid);
this.features = new this.Features();
this.enableCarbons();
this.initStatus(function () {
this.registerIntervalHandler();
this.roster = new this.RosterContacts();
this.roster.browserStorage = new Backbone.BrowserStorage[this.storage](
b64_sha1('converse.contacts-'+this.bare_jid));
this.chatboxes.onConnected();
this.giveFeedback(__('Contacts'));
if (typeof this.callback === 'function') {
// A callback method may be passed in via the
// converse.initialize method.
// XXX: Can we use $.Deferred instead of this callback?
if (this.connection.service === 'jasmine tests') {
// XXX: Call back with the internal converse object. This
// object should never be exposed to production systems.
// 'jasmine tests' is an invalid http bind service value,
// so we're sure that this is just for tests.
this.callback(this);
} else {
this.callback();
}
}
deferred.resolve();
}.bind(this));
converse.emit('ready');
this.initStatus().done(_.bind(this.onStatusInitialized, this, deferred));
converse.emit('connected');
converse.emit('ready'); // BBB: Will be removed.
return deferred.promise();
};
@ -933,7 +897,7 @@
* (String) jid - The Jabber ID of the user being added
* (String) name - The name of that user
* (Array of Strings) groups - Any roster groups the user might belong to
* (Function) callback - A function to call once the VCard is returned
* (Function) callback - A function to call once the IQ is returned
* (Function) errback - A function to call if an error occured
*/
name = _.isEmpty(name)? jid: name;
@ -1118,31 +1082,33 @@
}
},
createRequestingContactFromVCard: function (iq, jid, fullname, img, img_type, url) {
/* A contact request was recieved, and we then asked for the
* VCard of that user.
createRequestingContact: function (presence) {
/* Creates a Requesting Contact.
*
* Note: this method gets completely overridden by converse-vcard.js
*/
var bare_jid = Strophe.getBareJidFromJid(jid);
var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
var nick = $(presence).children('nick[xmlns='+Strophe.NS.NICK+']').text();
var user_data = {
jid: bare_jid,
subscription: 'none',
ask: null,
requesting: true,
fullname: fullname || bare_jid,
image: img,
image_type: img_type,
url: url,
vcard_updated: moment().format()
fullname: nick || bare_jid,
};
this.create(user_data);
converse.emit('contactRequest', user_data);
},
handleIncomingSubscription: function (jid) {
handleIncomingSubscription: function (presence) {
var jid = presence.getAttribute('from');
var bare_jid = Strophe.getBareJidFromJid(jid);
var contact = this.get(bare_jid);
if (!converse.allow_contact_requests) {
converse.rejectPresenceSubscription(jid, __("This client does not allow presence subscriptions"));
converse.rejectPresenceSubscription(
jid,
__("This client does not allow presence subscriptions")
);
}
if (converse.auto_subscribe) {
if ((!contact) || (contact.get('subscription') !== 'to')) {
@ -1158,13 +1124,7 @@
contact.authorize();
}
} else if (!contact) {
converse.getVCard(
bare_jid, this.createRequestingContactFromVCard.bind(this),
function (iq, jid) {
converse.log("Error while retrieving vcard for "+jid);
this.createRequestingContactFromVCard.call(this, iq, jid);
}.bind(this)
);
this.createRequestingContact(presence);
}
}
},
@ -1199,7 +1159,7 @@
} else if (presence_type === 'unsubscribe') {
return;
} else if (presence_type === 'subscribe') {
this.handleIncomingSubscription(jid);
this.handleIncomingSubscription(presence);
} else if (presence_type === 'unavailable' && contact) {
// Only set the user to offline if there aren't any
// other resources still available.
@ -1487,14 +1447,6 @@
'status' : this.getStatus()
});
this.on('change', function (item) {
if (this.get('fullname') === undefined) {
converse.getVCard(
null, // No 'to' attr when getting one's own vCard
function (iq, jid, fullname, image, image_type, url) {
this.save({'fullname': fullname});
}.bind(this)
);
}
if (_.has(item.changed, 'status')) {
converse.emit('statusChanged', this.get('status'));
}
@ -1617,9 +1569,6 @@
converse.connection.disco.addFeature(Strophe.NS.CHATSTATES);
converse.connection.disco.addFeature(Strophe.NS.DISCO_INFO);
converse.connection.disco.addFeature(Strophe.NS.ROSTERX); // Limited support
if (converse.use_vcards) {
converse.connection.disco.addFeature(Strophe.NS.VCARD);
}
if (converse.message_carbons) {
converse.connection.disco.addFeature(Strophe.NS.CARBONS);
}

View File

@ -199,7 +199,7 @@
converse.on('contactRequest', converse.handleContactRequestNotification);
converse.on('contactStatusChanged', converse.handleChatStateNotification);
converse.on('message', converse.handleMessageNotification);
converse.on('ready', converse.requestPermission);
converse.on('connected', converse.requestPermission);
}
});
}));

174
src/converse-vcard.js Normal file
View File

@ -0,0 +1,174 @@
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) {
define("converse-vcard", [
"converse-core",
"converse-api",
"strophe.vcard",
], factory);
}(this, function (converse, converse_api) {
"use strict";
var Strophe = converse_api.env.Strophe,
$ = converse_api.env.jQuery,
_ = converse_api.env._,
moment = converse_api.env.moment;
converse_api.plugins.add('vcard', {
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
Features: {
addClientFeatures: function () {
this._super.addClientFeatures.apply(this, arguments);
if (converse.use_vcards) {
converse.connection.disco.addFeature(Strophe.NS.VCARD);
}
}
},
RosterContacts: {
createRequestingContact: function (presence) {
var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
converse.getVCard(
bare_jid,
_.partial(converse.createRequestingContactFromVCard, presence),
function (iq, jid) {
converse.log("Error while retrieving vcard for "+jid);
converse.createRequestingContactFromVCard(presence, iq, jid);
}
);
}
}
},
initialize: function () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
this.updateSettings({
use_vcards: true,
});
converse.createRequestingContactFromVCard = function (presence, iq, jid, fullname, img, img_type, url) {
var bare_jid = Strophe.getBareJidFromJid(jid);
var nick = $(presence).children('nick[xmlns="'+Strophe.NS.NICK+'"]').text();
var user_data = {
jid: bare_jid,
subscription: 'none',
ask: null,
requesting: true,
fullname: fullname || nick || bare_jid,
image: img,
image_type: img_type,
url: url,
vcard_updated: moment().format()
};
converse.roster.create(user_data);
converse.emit('contactRequest', user_data);
};
converse.onVCardError = function (jid, iq, errback) {
var contact = converse.roster.get(jid);
if (contact) {
contact.save({ 'vcard_updated': moment().format() });
}
if (errback) { errback(iq, jid); }
};
converse.onVCardData = function (jid, iq, callback) {
var $vcard = $(iq).find('vCard'),
fullname = $vcard.find('FN').text(),
img = $vcard.find('BINVAL').text(),
img_type = $vcard.find('TYPE').text(),
url = $vcard.find('URL').text();
if (jid) {
var contact = converse.roster.get(jid);
if (contact) {
fullname = _.isEmpty(fullname)? contact.get('fullname') || jid: fullname;
contact.save({
'fullname': fullname,
'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));
}
};
var updateVCardForChatBox = function (evt, chatbox) {
if (!converse.use_vcards) { return; }
var 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"
);
}
);
}
};
converse.on('chatBoxInitialized', updateVCardForChatBox);
var fetchOwnVCard = function () {
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.on('statusInitialized', fetchOwnVCard);
}
});
}));