فهرست منبع

Move VCard functionality into separate plugin

JC Brand 9 سال پیش
والد
کامیت
08222182c5
7فایلهای تغییر یافته به همراه261 افزوده شده و 149 حذف شده
  1. 3 1
      converse.js
  2. 15 11
      docs/source/development.rst
  3. 12 7
      spec/protocol.js
  4. 2 24
      src/converse-chatview.js
  5. 54 105
      src/converse-core.js
  6. 1 1
      src/converse-notification.js
  7. 174 0
      src/converse-vcard.js

+ 3 - 1
converse.js

@@ -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

+ 15 - 11
docs/source/development.rst

@@ -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) { ... });``                   |

+ 12 - 7
spec/protocol.js

@@ -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")');

+ 2 - 24
src/converse-chatview.js

@@ -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;

+ 54 - 105
src/converse-core.js

@@ -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);
                 }

+ 1 - 1
src/converse-notification.js

@@ -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 - 0
src/converse-vcard.js

@@ -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);
+        }
+    });
+}));