Răsfoiți Sursa

Merge branch 'master' into gh-pages

Conflicts:
	index.html
JC Brand 12 ani în urmă
părinte
comite
4caabf5013
6 a modificat fișierele cu 341 adăugiri și 52 ștergeri
  1. 232 0
      Libraries/strophe.disco.js
  2. 0 1
      Libraries/strophe.js
  3. 13 5
      converse.css
  4. 88 36
      converse.js
  5. 7 10
      index.html
  6. 1 0
      main.js

+ 232 - 0
Libraries/strophe.disco.js

@@ -0,0 +1,232 @@
+/*
+  Copyright 2010, François de Metz <francois@2metz.fr>
+*/
+
+/**
+ * Disco Strophe Plugin
+ * Implement http://xmpp.org/extensions/xep-0030.html
+ * TODO: manage node hierarchies, and node on info request
+ */
+Strophe.addConnectionPlugin('disco',
+{
+    _connection: null,
+    _identities : [],
+    _features : [],
+    _items : [],
+    /** Function: init
+     * Plugin init
+     *
+     * Parameters:
+     *   (Strophe.Connection) conn - Strophe connection
+     */
+    init: function(conn)
+    {
+    this._connection = conn;
+        this._identities = [];
+        this._features   = [];
+        this._items      = [];
+        // disco info
+        conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
+        // disco items
+        conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null);
+    },
+    /** Function: addIdentity
+     * See http://xmpp.org/registrar/disco-categories.html
+     * Parameters:
+     *   (String) category - category of identity (like client, automation, etc ...)
+     *   (String) type - type of identity (like pc, web, bot , etc ...)
+     *   (String) name - name of identity in natural language
+     *   (String) lang - lang of name parameter
+     *
+     * Returns:
+     *   Boolean
+     */
+    addIdentity: function(category, type, name, lang)
+    {
+        for (var i=0; i<this._identities.length; i++)
+        {
+            if (this._identities[i].category == category &&
+                this._identities[i].type == type &&
+                this._identities[i].name == name &&
+                this._identities[i].lang == lang)
+            {
+                return false;
+            }
+        }
+        this._identities.push({category: category, type: type, name: name, lang: lang});
+        return true;
+    },
+    /** Function: addFeature
+     *
+     * Parameters:
+     *   (String) var_name - feature name (like jabber:iq:version)
+     *
+     * Returns:
+     *   boolean
+     */
+    addFeature: function(var_name)
+    {
+        for (var i=0; i<this._features.length; i++)
+        {
+             if (this._features[i] == var_name)
+                 return false;
+        }
+        this._features.push(var_name);
+        return true;
+    },
+    /** Function: removeFeature
+     *
+     * Parameters:
+     *   (String) var_name - feature name (like jabber:iq:version)
+     *
+     * Returns:
+     *   boolean
+     */
+    removeFeature: function(var_name)
+    {
+        for (var i=0; i<this._features.length; i++)
+        {
+             if (this._features[i] === var_name){
+                 this._features.splice(i,1)
+                 return true;
+             }
+        }
+        return false;
+    },
+    /** Function: addItem
+     *
+     * Parameters:
+     *   (String) jid
+     *   (String) name
+     *   (String) node
+     *   (Function) call_back
+     *
+     * Returns:
+     *   boolean
+     */
+    addItem: function(jid, name, node, call_back)
+    {
+        if (node && !call_back)
+            return false;
+        this._items.push({jid: jid, name: name, node: node, call_back: call_back});
+        return true;
+    },
+    /** Function: info
+     * Info query
+     *
+     * Parameters:
+     *   (Function) call_back
+     *   (String) jid
+     *   (String) node
+     */
+    info: function(jid, node, success, error, timeout)
+    {
+        var attrs = {xmlns: Strophe.NS.DISCO_INFO};
+        if (node)
+            attrs.node = node;
+
+        var info = $iq({from:this._connection.jid,
+                         to:jid, type:'get'}).c('query', attrs);
+        this._connection.sendIQ(info, success, error, timeout);
+    },
+    /** Function: items
+     * Items query
+     *
+     * Parameters:
+     *   (Function) call_back
+     *   (String) jid
+     *   (String) node
+     */
+    items: function(jid, node, success, error, timeout)
+    {
+        var attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
+        if (node)
+            attrs.node = node;
+
+        var items = $iq({from:this._connection.jid,
+                         to:jid, type:'get'}).c('query', attrs);
+        this._connection.sendIQ(items, success, error, timeout);
+    },
+
+    /** PrivateFunction: _buildIQResult
+     */
+    _buildIQResult: function(stanza, query_attrs)
+    {
+        var id   =  stanza.getAttribute('id');
+        var from = stanza.getAttribute('from');
+        var iqresult = $iq({type: 'result', id: id});
+
+        if (from !== null) {
+            iqresult.attrs({to: from});
+        }
+
+        return iqresult.c('query', query_attrs);
+    },
+
+    /** PrivateFunction: _onDiscoInfo
+     * Called when receive info request
+     */
+    _onDiscoInfo: function(stanza)
+    {
+        var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
+        var attrs = {xmlns: Strophe.NS.DISCO_INFO};
+        if (node)
+        {
+            attrs.node = node;
+        }
+        var iqresult = this._buildIQResult(stanza, attrs);
+        for (var i=0; i<this._identities.length; i++)
+        {
+            var attrs = {category: this._identities[i].category,
+                         type    : this._identities[i].type};
+            if (this._identities[i].name)
+                attrs.name = this._identities[i].name;
+            if (this._identities[i].lang)
+                attrs['xml:lang'] = this._identities[i].lang;
+            iqresult.c('identity', attrs).up();
+        }
+        for (var i=0; i<this._features.length; i++)
+        {
+            iqresult.c('feature', {'var':this._features[i]}).up();
+        }
+        this._connection.send(iqresult.tree());
+        return true;
+    },
+    /** PrivateFunction: _onDiscoItems
+     * Called when receive items request
+     */
+    _onDiscoItems: function(stanza)
+    {
+        var query_attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
+        var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
+        if (node)
+        {
+            query_attrs.node = node;
+            var items = [];
+            for (var i = 0; i < this._items.length; i++)
+            {
+                if (this._items[i].node == node)
+                {
+                    items = this._items[i].call_back(stanza);
+                    break;
+                }
+            }
+        }
+        else
+        {
+            var items = this._items;
+        }
+        var iqresult = this._buildIQResult(stanza, query_attrs);
+        for (var i = 0; i < items.length; i++)
+        {
+            var attrs = {jid:  items[i].jid};
+            if (items[i].name)
+                attrs.name = items[i].name;
+            if (items[i].node)
+                attrs.node = items[i].node;
+            iqresult.c('item', attrs).up();
+        }
+        this._connection.send(iqresult.tree());
+        return true;
+    }
+});

+ 0 - 1
Libraries/strophe.js

@@ -1917,7 +1917,6 @@ Strophe.Handler.prototype = {
         try {
             result = this.handler(elem);
         } catch (e) {
-            result = this.handler(elem);
             if (e.sourceURL) {
                 Strophe.fatal("error: " + this.handler +
                               " " + e.sourceURL + ":" +

+ 13 - 5
converse.css

@@ -415,6 +415,19 @@ form.search-xmpp-contact input {
     margin-top: 0.5em;
 }
 
+#available-chatrooms {
+    height: 225px;
+    overflow-y: scroll;
+}
+
+#available-chatrooms dd {
+    overflow-x: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: inline-block;
+    width: 160px;
+}
+
 #available-chatrooms dt,
 #converse-roster dt {
     font-weight: normal;
@@ -430,11 +443,6 @@ form.search-xmpp-contact input {
     padding-top: 1em;
 }
 
-#available-chatrooms li {
-    display: block;
-}
-
-
 dd.available-chatroom,
 #converse-roster dd {
     font-weight: bold;

+ 88 - 36
converse.js

@@ -1,8 +1,8 @@
 /*!
- * Converse.js (XMPP-based instant messaging with Strophe.js and backbone.js)
+ * Converse.js (Web-based XMPP instant messaging client)
  * http://conversejs.org
  *
- * Copyright (c) 2012 Jan-Carel Brand (jc@opkode.com)
+ * Copyright (c) 2012, Jan-Carel Brand <jc@opkode.com>
  * Dual licensed under the MIT and GPL Licenses
  */
 
@@ -22,7 +22,8 @@
                 "strophe": "Libraries/strophe",
                 "strophe.muc": "Libraries/strophe.muc",
                 "strophe.roster": "Libraries/strophe.roster",
-                "strophe.vcard": "Libraries/strophe.vcard"
+                "strophe.vcard": "Libraries/strophe.vcard",
+                "strophe.disco": "Libraries/strophe.disco"
             },
 
             // define module dependencies for modules not using define
@@ -38,22 +39,11 @@
                     //module value.
                     exports: 'Backbone'
                 },
-
-                'underscore': {
-                    exports: '_'
-                },
-
-                'strophe.muc': {
-                    deps: ['strophe', 'jquery']
-                },
-
-                'strophe.roster': {
-                    deps: ['strophe', 'jquery']
-                },
-
-                'strophe.vcard': {
-                    deps: ['strophe', 'jquery']
-                }
+                'underscore':   { exports: '_' },
+                'strophe.muc':  { deps: ['strophe', 'jquery'] },
+                'strophe.roster':   { deps: ['strophe', 'jquery'] },
+                'strophe.vcard':    { deps: ['strophe', 'jquery'] },
+                'strophe.disco':    { deps: ['strophe', 'jquery'] }
             }
         });
 
@@ -63,7 +53,8 @@
             "sjcl",
             "strophe.muc",
             "strophe.roster",
-            "strophe.vcard"
+            "strophe.vcard",
+            "strophe.disco"
             ], function() {
                 // Use Mustache style syntax for variable interpolation
                 _.templateSettings = {
@@ -82,7 +73,6 @@
         root.converse = factory(jQuery, _, console || {log: function(){}});
     }
 }(this, function ($, _, console) {
-
     var converse = {};
     converse.msg_counter = 0;
 
@@ -728,11 +718,10 @@
             this.on('update-rooms-list', function (ev) {
                 this.updateRoomsList();
             });
-            this.trigger('update-rooms-list');
         },
 
         updateRoomsList: function () {
-            converse.connection.muc.listRooms(converse.muc_domain, $.proxy(function (iq) {
+            converse.connection.muc.listRooms(this.muc_domain, $.proxy(function (iq) {
                 var name, jid, i,
                     rooms = $(iq).find('query').find('item'),
                     rooms_length = rooms.length,
@@ -744,7 +733,7 @@
                     $available_chatrooms.find('dt').hide();
                 }
                 for (i=0; i<rooms_length; i++) {
-                    name = Strophe.unescapeNode($(rooms[i]).attr('name'));
+                    name = Strophe.unescapeNode($(rooms[i]).attr('name')||$(rooms[i]).attr('jid'));
                     jid = $(rooms[i]).attr('jid');
                     $available_chatrooms.append(this.room_template({'name':name, 'jid':jid}));
                 }
@@ -762,7 +751,7 @@
                 name = input.val().trim().toLowerCase();
                 input.val(''); // Clear the input
                 if (name) {
-                    jid = Strophe.escapeNode(name) + '@' + converse.muc_domain;
+                    jid = Strophe.escapeNode(name) + '@' + this.muc_domain;
                 } else {
                     return;
                 }
@@ -790,8 +779,16 @@
         initialize: function () {
             this.$el.appendTo(converse.chatboxesview.$el);
             this.model.on('change', $.proxy(function (item, changed) {
+                var i;
                 if (_.has(item.changed, 'connected')) {
                     this.render();
+                    converse.features.on('add', $.proxy(this.featureAdded, this));
+                    // Features could have been added before the controlbox was
+                    // initialized. Currently we're only interested in MUC
+                    var feature = converse.features.findWhere({'var': 'http://jabber.org/protocol/muc'});
+                    if (feature) {
+                        this.featureAdded(feature);
+                    }
                 }
                 if (_.has(item.changed, 'visible')) {
                     if (item.changed.visible === true) {
@@ -799,7 +796,6 @@
                     }
                 }
             }, this));
-
             this.model.on('show', this.show, this);
             this.model.on('destroy', this.hide, this);
             this.model.on('hide', this.hide, this);
@@ -808,6 +804,17 @@
             }
         },
 
+        featureAdded: function (feature) {
+            if (feature.get('var') == 'http://jabber.org/protocol/muc') {
+                if (!this.roomspanel) {
+                    this.roomspanel = new converse.RoomsPanel();
+                    this.roomspanel.muc_domain = feature.get('from');
+                    this.roomspanel.$parent = this.$el;
+                    this.roomspanel.render().trigger('update-rooms-list');
+                }
+            }
+        },
+
         template: _.template(
             '<div class="chat-head oc-chat-head">'+
                 '<ul id="controlbox-tabs"></ul>'+
@@ -847,10 +854,6 @@
                 this.contactspanel = new converse.ContactsPanel();
                 this.contactspanel.$parent = this.$el;
                 this.contactspanel.render();
-                // TODO: Only add the rooms panel if the server supports MUC
-                this.roomspanel = new converse.RoomsPanel();
-                this.roomspanel.$parent = this.$el;
-                this.roomspanel.render();
             }
             return this;
         }
@@ -1842,8 +1845,7 @@
                             }));
             // iterate through all the <option> elements and add option values
             options.each(function(){
-                options_list.push(that.option_template({
-                                                        'value': $(this).val(),
+                options_list.push(that.option_template({'value': $(this).val(),
                                                         'text': $(this).text()
                                                         }));
             });
@@ -1854,6 +1856,57 @@
         }
     });
 
+    converse.Feature = Backbone.Model.extend();
+    converse.Features = Backbone.Collection.extend({
+        /* This collection stores Feature Models, representing features
+         * provided by available XMPP entities (e.g. servers)
+         *
+         * See XEP-0030 for more details: http://xmpp.org/extensions/xep-0030.html
+         */
+        model: converse.Feature,
+        initialize: function () {
+            this.localStorage = new Backbone.LocalStorage(
+                hex_sha1('converse.features'+converse.bare_jid));
+            if (this.localStorage.records.length === 0) {
+                // localStorage is empty, so we've likely never queried this
+                // domain for features yet
+                converse.connection.disco.info(converse.domain, null, this.onInfo, this.onError);
+                converse.connection.disco.items(converse.domain, null, $.proxy(this.onItems, this), $.proxy(this.onError, this));
+            } else {
+                this.fetch({add:true});
+            }
+        },
+
+        onItems: function (stanza) {
+            $(stanza).find('query item').each($.proxy(function (idx, item) {
+                converse.connection.disco.info(
+                    $(item).attr('jid'),
+                    null,
+                    $.proxy(this.onInfo, this),
+                    $.proxy(this.onError, this));
+            }, this));
+        },
+
+        onInfo: function (stanza) {
+            var $stanza = $(stanza);
+            if (($stanza.find('identity[category=server][type=im]').length === 0) &&
+                ($stanza.find('identity[category=conference][type=text]').length === 0)) {
+                // This isn't an IM server component
+                return;
+            }
+            $stanza.find('feature').each($.proxy(function (idx, feature) {
+                this.create({
+                    'var': $(feature).attr('var'),
+                    'from': $stanza.attr('from')
+                });
+            }, this));
+        },
+
+        onError: function (stanza) {
+            console.log("Error while doing service discovery");
+        }
+    });
+
     converse.LoginPanel = Backbone.View.extend({
         tagName: 'div',
         id: "login-dialog",
@@ -1868,7 +1921,7 @@
             '<input type="text" id="jid">' +
             '<label>Password:</label>' +
             '<input type="password" id="password">' +
-            '<input type="submit" name="submit"/>' +
+            '<button type="submit">Log In</button>' +
             '</form">'),
 
         bosh_url_input: _.template(
@@ -1946,6 +1999,7 @@
                 template.find('form').append(this.bosh_url_input);
             }
             this.$parent.find('#controlbox-panes').append(this.$el.html(template));
+            this.$el.find('input#jid').focus();
             return this;
         }
     });
@@ -1985,13 +2039,11 @@
 
     converse.onConnected = function (connection) {
         this.connection = connection;
-
-        this.animate = true; // Use animations
         this.connection.xmlInput = function (body) { console.log(body); };
         this.connection.xmlOutput = function (body) { console.log(body); };
         this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
         this.domain = Strophe.getDomainFromJid(this.connection.jid);
-        this.muc_domain = 'conference.' +  this.domain;
+        this.features = new this.Features();
 
         // Set up the roster
         this.roster = new this.RosterItems();

+ 7 - 10
index.html

@@ -43,18 +43,19 @@
 
     <h2>Features</h2>
     <ul>
-        <li>Single and multi-user chat</li>
+        <li>Single-user chat</li>
+        <li>Multi-user chat in chatrooms (<a href="http://xmpp.org/extensions/xep-0045.html">XEP 45</a>)</li>
+        <li>vCard support (<a href="http://xmpp.org/extensions/xep-0054.html">XEP 54</a>)</li>
+        <li>Service discovery (<a href="http://xmpp.org/extensions/xep-0030.html">XEP 30</a>)</li>
         <li>Contact rosters</li>
         <li>Manually or automically subscribe to other contacts</li>
-        <li>Roster item exchange (<a href="http://xmpp.org/extensions/tmp/xep-0144-1.1.html">XEP 144</a>)</li>
         <li>Accept or decline contact requests</li>
+        <li>Roster item exchange (<a href="http://xmpp.org/extensions/tmp/xep-0144-1.1.html">XEP 144</a>)</li>
         <li>Chat statuses (online, busy, away, offline)</li>
         <li>Custom status messages</li>
         <li>Typing notifications</li>
         <li>Third person messages (/me )</li>
-        <li>Multi-user chat in chatrooms (<a href="http://xmpp.org/extensions/xep-0045.html">XEP 45</a>)</li>
         <li>Chatroom Topics</li>
-        <li>vCard support (<a href="http://xmpp.org/extensions/xep-0054.html">XEP 54</a>)</li>
     </ul>
 
     <h2>CMS Integration</h2>
@@ -76,8 +77,7 @@
 
     <h2>Demo</h2>
     <p><a href="#" class="chat toggle-online-users">Click this link</a> or click the link on the bottom right corner of this page.</a></p>
-    <p>
-        You can log in with any existing federated Jabber/XMPP account, or create a new one at any of these providers:
+    <p>You can log in with any existing federated Jabber/XMPP account, or create a new one at any of these providers:
         <ul>
             <li><a href="http://jabber.org" target="_blank">jabber.org</a></li>
             <li><a href="https://jappix.com" target="_blank">jappix.com</a></li>
@@ -98,10 +98,7 @@
         establish an authenticated connection on the server side and then attach to
         this connection in your browser.
    </p>
-    <p><strong>Converse.js</strong> already supports this usecase, but you'll have to
-    do more manual work yourself.
-    </p>
-
+    <p><strong>Converse.js</strong> already supports this usecase, but you'll have to do more manual work yourself.</p>
 
     <h2>Tests</h2>
    </p>

+ 1 - 0
main.js

@@ -1,5 +1,6 @@
 require(["jquery", "converse"], function($, converse) {
     converse.initialize({
+        animate: true,
         bosh_service_url: 'https://bind.opkode.im',
         prebind: false,
         xhr_user_search: false,