Add initial service discovery (XEP 30) support.

- Still needs caching and optimisation
This commit is contained in:
JC Brand 2013-04-25 23:51:55 +02:00
parent bf5c5e36dc
commit 62a2e307a5
4 changed files with 305 additions and 35 deletions

232
Libraries/strophe.disco.js Normal file
View File

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

View File

@ -415,6 +415,19 @@ form.search-xmpp-contact input {
margin-top: 0.5em; 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, #available-chatrooms dt,
#converse-roster dt { #converse-roster dt {
font-weight: normal; font-weight: normal;
@ -430,11 +443,6 @@ form.search-xmpp-contact input {
padding-top: 1em; padding-top: 1em;
} }
#available-chatrooms li {
display: block;
}
dd.available-chatroom, dd.available-chatroom,
#converse-roster dd { #converse-roster dd {
font-weight: bold; font-weight: bold;

View File

@ -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 * 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 * Dual licensed under the MIT and GPL Licenses
*/ */
@ -22,7 +22,8 @@
"strophe": "Libraries/strophe", "strophe": "Libraries/strophe",
"strophe.muc": "Libraries/strophe.muc", "strophe.muc": "Libraries/strophe.muc",
"strophe.roster": "Libraries/strophe.roster", "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 // define module dependencies for modules not using define
@ -38,22 +39,11 @@
//module value. //module value.
exports: 'Backbone' exports: 'Backbone'
}, },
'underscore': { exports: '_' },
'underscore': { 'strophe.muc': { deps: ['strophe', 'jquery'] },
exports: '_' 'strophe.roster': { deps: ['strophe', 'jquery'] },
}, 'strophe.vcard': { deps: ['strophe', 'jquery'] },
'strophe.disco': { deps: ['strophe', 'jquery'] }
'strophe.muc': {
deps: ['strophe', 'jquery']
},
'strophe.roster': {
deps: ['strophe', 'jquery']
},
'strophe.vcard': {
deps: ['strophe', 'jquery']
}
} }
}); });
@ -63,7 +53,8 @@
"sjcl", "sjcl",
"strophe.muc", "strophe.muc",
"strophe.roster", "strophe.roster",
"strophe.vcard" "strophe.vcard",
"strophe.disco"
], function() { ], function() {
// Use Mustache style syntax for variable interpolation // Use Mustache style syntax for variable interpolation
_.templateSettings = { _.templateSettings = {
@ -82,7 +73,6 @@
root.converse = factory(jQuery, _, console || {log: function(){}}); root.converse = factory(jQuery, _, console || {log: function(){}});
} }
}(this, function ($, _, console) { }(this, function ($, _, console) {
var converse = {}; var converse = {};
converse.msg_counter = 0; converse.msg_counter = 0;
@ -744,7 +734,7 @@
$available_chatrooms.find('dt').hide(); $available_chatrooms.find('dt').hide();
} }
for (i=0; i<rooms_length; i++) { 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'); jid = $(rooms[i]).attr('jid');
$available_chatrooms.append(this.room_template({'name':name, 'jid':jid})); $available_chatrooms.append(this.room_template({'name':name, 'jid':jid}));
} }
@ -847,12 +837,17 @@
this.contactspanel = new converse.ContactsPanel(); this.contactspanel = new converse.ContactsPanel();
this.contactspanel.$parent = this.$el; this.contactspanel.$parent = this.$el;
this.contactspanel.render(); this.contactspanel.render();
// TODO: Only add the rooms panel if the server supports MUC }
return this;
},
addRoomsPanel: function () {
// XXX: We might to ensure that render was already called
if (!this.roomspanel) {
this.roomspanel = new converse.RoomsPanel(); this.roomspanel = new converse.RoomsPanel();
this.roomspanel.$parent = this.$el; this.roomspanel.$parent = this.$el;
this.roomspanel.render(); this.roomspanel.render();
} }
return this;
} }
}); });
@ -1842,8 +1837,7 @@
})); }));
// iterate through all the <option> elements and add option values // iterate through all the <option> elements and add option values
options.each(function(){ options.each(function(){
options_list.push(that.option_template({ options_list.push(that.option_template({'value': $(this).val(),
'value': $(this).val(),
'text': $(this).text() 'text': $(this).text()
})); }));
}); });
@ -1854,6 +1848,41 @@
} }
}); });
converse.ServiceDiscovery = Backbone.Model.extend({
initialize: function () {
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));
},
onItems: function (stanza) {
var that = this;
$(stanza).find('query item').each(function () {
converse.connection.disco.info($(this).attr('jid'), null, that.onInfo, that.onError);
});
},
onInfo: function (stanza) {
var $stanza = $(stanza);
if ($(stanza).find('identity[category=conference][type=text]').length < 1) {
// This isn't an IM server component
return;
}
$stanza.find('feature').each(function (idx, feature) {
if ($(this).attr('var') == 'http://jabber.org/protocol/muc') {
// This component supports MUC
converse.muc_domain = $stanza.attr('from');
// XXX: It would probably make sense to refactor a
// controlbox to be a Collection of Panel objects
converse.chatboxesview.views.controlbox.addRoomsPanel();
}
});
},
onError: function (stanza) {
console.log("Error while doing service discovery");
}
});
converse.LoginPanel = Backbone.View.extend({ converse.LoginPanel = Backbone.View.extend({
tagName: 'div', tagName: 'div',
id: "login-dialog", id: "login-dialog",
@ -1868,7 +1897,7 @@
'<input type="text" id="jid">' + '<input type="text" id="jid">' +
'<label>Password:</label>' + '<label>Password:</label>' +
'<input type="password" id="password">' + '<input type="password" id="password">' +
'<input type="submit" name="submit"/>' + '<button type="submit">Log In</button>' +
'</form">'), '</form">'),
bosh_url_input: _.template( bosh_url_input: _.template(
@ -1985,13 +2014,13 @@
converse.onConnected = function (connection) { converse.onConnected = function (connection) {
this.connection = connection; this.connection = connection;
this.animate = true; // Use animations
this.connection.xmlInput = function (body) { console.log(body); }; this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); }; this.connection.xmlOutput = function (body) { console.log(body); };
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid); this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
this.domain = Strophe.getDomainFromJid(this.connection.jid); this.domain = Strophe.getDomainFromJid(this.connection.jid);
this.muc_domain = 'conference.' + this.domain;
// Sevice discovery
this.disco = new this.ServiceDiscovery();
// Set up the roster // Set up the roster
this.roster = new this.RosterItems(); this.roster = new this.RosterItems();

View File

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