Mercurial > eldonilo > lightstring
changeset 0:96898e3812a5
initial push
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Sun, 18 Dec 2011 19:03:28 +0100 |
parents | |
children | 96087680669f |
files | XMPP.js base64.js md5.js plugins.js |
diffstat | 4 files changed, 834 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/XMPP.js @@ -0,0 +1,221 @@ +'use strict'; + +var Lightstring = { + NS: {}, + stanza: {}, +}; + +Lightstring.Connection = function (aURL) { + var parser = new DOMParser(); + var serializer = new XMLSerializer(); + this.handlers = {}; + this.iqid = 1024; + this.getNewId = function() { + this.iqid++; + return 'sendiq:'+this.iqid; + }; + this.parse = function(str) { + return parser.parseFromString(str, 'text/xml').documentElement; + }; + this.serialize = function(elm) { + return serializer.serializeToString(elm); + }; + this.connect = function(jid, password) { + this.domain = jid.split('@')[1]; + this.node = jid.split('@')[0]; + this.jid = jid; + this.password = password; + if(typeof WebSocket === 'undefined') + this.socket = new MozWebSocket(aURL); + else + this.socket = new WebSocket(aURL); + + var that = this; + this.socket.addEventListener('open', function() { + that.emit('open'); + that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); + }); + this.socket.addEventListener('error', function(err) { + that.emit('error'); + }); + this.socket.addEventListener('close', function(close) { + that.emit('close'); + that.emit('disconnected'); + }); + this.socket.addEventListener('message', function(e) { + that.emit('XMLInput', e.data); + var elm = that.parse(e.data); + that.emit('DOMInput', elm); + that.emit(elm.tagName, elm); + + if((elm.tagName === 'iq')) + that.emit(elm.getAttribute('id'), elm); + }); + }; + + + + this.send = function(stanza, callback) { + //FIXME support for E4X + //~ if(typeof stanza === 'xml') { + //~ stanza = stanza.toXMLString(); + //~ } + if(stanza.cnode) { + stanza = stanza.toString(); + //~ console.log(typeof stanza); + } + if(typeof stanza === 'string') { + var str = stanza; + var elm = this.parse(stanza); + } + else { + var elm = stanza; + var str = this.serialize(stanza); + } + + + if(elm.tagName === 'iq') { + var id = elm.getAttribute('id'); + if(!id) { + elm.setAttribute('id', this.getNewId()) + str = this.serialize(elm) + } + if(callback) this.on(elm.getAttribute('id'), callback); + } + + + this.socket.send(str); + this.emit('XMLOutput', str); + this.emit('DOMOutput', elm); + }; + this.disconnect = function() { + this.send('</stream:stream>'); + this.emit('disconnected'); + this.socket.close(); + }; + //FIXME Callbacks sucks, better idea? + this.emit = function(name, data) { + var handlers = this.handlers[name]; + if(!handlers) + return; + + //FIXME Better idea than passing the context as argument? + for(var i=0; i<handlers.length; i++) + handlers[i](data, this); + + if(name.match('sendiq:')) + delete this.handlers[name]; + }; + this.on = function(name, callback) { + if(!this.handlers[name]) + this.handlers[name] = []; + this.handlers[name].push(callback); + }; + //FIXME do this! + this.once = function(name, callback) { + if(!this.handlers[name]) + this.handlers[name] = []; + this.handlers[name].push(callback); + }; + //Internal + this.on('stream:features', function(stanza, that) { + var nodes = stanza.querySelectorAll('mechanism'); + //SASL/Auth features + if(nodes.length > 0) { + that.emit('mechanisms', stanza); + var mechanisms = {}; + for(var i=0; i<nodes.length; i++) + mechanisms[nodes[i].textContent] = true; + + + //FIXME support SCRAM-SHA1 && allow specify method preferences + if('DIGEST-MD5' in mechanisms) + that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); + else if('PLAIN' in mechanisms) { + var token = btoa(that.jid + "\u0000" + that.jid.split('@')[0] + "\u0000" + that.password); + that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+token+"</auth>"); + } + } + //XMPP features + else { + that.emit('features', stanza); + //Bind http://xmpp.org/rfcs/rfc3920.html#bind + that.send("<iq type='set' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>", function() { + //Session http://xmpp.org/rfcs/rfc3921.html#session + that.send("<iq type='set' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>", function() { + that.emit('connected'); + }); + }); + } + }); + //Internal + this.on('success', function(stanza, that) { + that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); + }); + //Internal + this.on('challenge', function(stanza, that) { + //FIXME this is mostly Strophe code + + function _quote(str) { + return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; + }; + + var challenge = atob(stanza.textContent); + + var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; + + var cnonce = MD5.hexdigest(Math.random() * 1234567890); + var realm = ""; + var host = null; + var nonce = ""; + var qop = ""; + var matches; + + while (challenge.match(attribMatch)) { + matches = challenge.match(attribMatch); + challenge = challenge.replace(matches[0], ""); + matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); + switch (matches[1]) { + case "realm": + realm = matches[2]; + break; + case "nonce": + nonce = matches[2]; + break; + case "qop": + qop = matches[2]; + break; + case "host": + host = matches[2]; + break; + } + } + + var digest_uri = "xmpp/" + that.domain; + if (host !== null) { + digest_uri = digest_uri + "/" + host; + } + + var A1 = MD5.hash(that.node + + ":" + realm + ":" + that.password) + + ":" + nonce + ":" + cnonce; + var A2 = 'AUTHENTICATE:' + digest_uri; + + var responseText = ""; + responseText += 'username=' + _quote(that.node) + ','; + responseText += 'realm=' + _quote(realm) + ','; + responseText += 'nonce=' + _quote(nonce) + ','; + responseText += 'cnonce=' + _quote(cnonce) + ','; + responseText += 'nc="00000001",'; + responseText += 'qop="auth",'; + responseText += 'digest-uri=' + _quote(digest_uri) + ','; + responseText += 'response=' + _quote( + MD5.hexdigest(MD5.hexdigest(A1) + ":" + + nonce + ":00000001:" + + cnonce + ":auth:" + + MD5.hexdigest(A2))) + ','; + responseText += 'charset="utf-8"'; + + that.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+btoa(responseText)+"</response>"); + }); +};
new file mode 100644 --- /dev/null +++ b/base64.js @@ -0,0 +1,80 @@ +// This code was written by Tyler Akins and has been placed in the +// public domain. It would be nice if you left this header intact. +// Base64 code from Tyler Akins -- http://rumkin.com + +var Base64 = (function () { + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + var obj = { + /** + * Encodes a string in base64 + * @param {String} input The string to encode in base64. + */ + encode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + } while (i < input.length); + + return output; + }, + + /** + * Decodes a base64 string. + * @param {String} input The string to decode. + */ + decode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } while (i < input.length); + + return output; + } + }; + + return obj; +})();
new file mode 100644 --- /dev/null +++ b/md5.js @@ -0,0 +1,276 @@ +/* + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +var MD5 = (function () { + /* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ + var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ + var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ + var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + + /* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + var safe_add = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + + /* + * Bitwise rotate a 32-bit number to the left. + */ + var bit_rol = function (num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); + }; + + /* + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + */ + var str2binl = function (str) { + var bin = []; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + { + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); + } + return bin; + }; + + /* + * Convert an array of little-endian words to a string + */ + var binl2str = function (bin) { + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + { + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); + } + return str; + }; + + /* + * Convert an array of little-endian words to a hex string. + */ + var binl2hex = function (binarray) { + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; + }; + + /* + * Convert an array of little-endian words to a base-64 string + */ + var binl2b64 = function (binarray) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + var triplet, j; + for(var i = 0; i < binarray.length * 4; i += 3) + { + triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) | + (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) | + ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for(j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; } + else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } + } + } + return str; + }; + + /* + * These functions implement the four basic operations the algorithm uses. + */ + var md5_cmn = function (q, a, b, x, s, t) { + return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); + }; + + var md5_ff = function (a, b, c, d, x, s, t) { + return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); + }; + + var md5_gg = function (a, b, c, d, x, s, t) { + return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); + }; + + var md5_hh = function (a, b, c, d, x, s, t) { + return md5_cmn(b ^ c ^ d, a, b, x, s, t); + }; + + var md5_ii = function (a, b, c, d, x, s, t) { + return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); + }; + + /* + * Calculate the MD5 of an array of little-endian words, and a bit length + */ + var core_md5 = function (x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + var olda, oldb, oldc, oldd; + for (var i = 0; i < x.length; i += 16) + { + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + } + return [a, b, c, d]; + }; + + + /* + * Calculate the HMAC-MD5, of a key and some data + */ + var core_hmac_md5 = function (key, data) { + var bkey = str2binl(key); + if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); } + + var ipad = new Array(16), opad = new Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); + return core_md5(opad.concat(hash), 512 + 128); + }; + + var obj = { + /* + * These are the functions you'll usually want to call. + * They take string arguments and return either hex or base-64 encoded + * strings. + */ + hexdigest: function (s) { + return binl2hex(core_md5(str2binl(s), s.length * chrsz)); + }, + + b64digest: function (s) { + return binl2b64(core_md5(str2binl(s), s.length * chrsz)); + }, + + hash: function (s) { + return binl2str(core_md5(str2binl(s), s.length * chrsz)); + }, + + hmac_hexdigest: function (key, data) { + return binl2hex(core_hmac_md5(key, data)); + }, + + hmac_b64digest: function (key, data) { + return binl2b64(core_hmac_md5(key, data)); + }, + + hmac_hash: function (key, data) { + return binl2str(core_hmac_md5(key, data)); + }, + + /* + * Perform a simple self-test to see if the VM is working + */ + test: function () { + return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72"; + } + }; + + return obj; +})();
new file mode 100644 --- /dev/null +++ b/plugins.js @@ -0,0 +1,257 @@ +'use strict'; + +// +//Roster +// +Lighstring.NS.roster = 'jabber:iq:roster'; +Lighstring.stanza.roster = { + 'get': function() { + return "<iq type='get'><query xmlns='"+Mango.NS.roster+"'/></iq>"; + }, + add: function(aAddress, aGroups, aCustomName) { + var iq = $iq({type: 'set'}).c('query', {xmlns: Mango.NS.roster}).c('item', {jid: aAddress}).tree(); + if(aCustomName) iq.querySelector('item').setAttribute(aCustomName); + for (var i=0; i<aGroups.length; i++) { + if(i === 0) iq.querySelector('item').appendChild(document.createElement('group')); + iq.querySelector('group').appendChild(document.createElement(aGroups[i])); + } + return iq; + }, + remove: function(aAddress) { + return $iq({type: 'set'}).c('query', {xmlns: Mango.NS.roster}).c('item', {jid: aAddress, subscription: 'remove'}).tree(); + } +}; +Lighstring.getRoster = function(connection, aCallback) { + connection.send(this.stanza.roster.get(), function(answer){ + var contacts = []; + answer.querySelectorAll('item').forEach(function(item) { + var jid = item.getAttribute('jid'); + var name = item.getAttribute('name'); + var groups = item.querySelectorAll('group'); + var subscription = item.getAttribute('subscription'); + var contact = {}; + if(name) + contact.name = name; + if(jid) + contact.jid = jid; + if(subscription) + contact.subscription = subscription; + if(groups.length > 0) { + contact.groups = []; + groups.forEach(function(group) { + contact.groups.push(group.textContent); + }); + } + + contacts.push(contact); + }); + aCallback(contacts); + }); +} +// +//vCard +// +Lighstring.NS.vcard = 'vcard-temp'; +Lighstring.stanza.vcard = { + 'get': function(aTo) { + if(aTo) + return "<iq type='get' to='"+aTo+"'><vCard xmlns='"+Mango.NS.vcard+"'/></iq>"; + else + return "<iq type='get'><vCard xmlns='"+Mango.NS.vcard+"'/></iq>"; + } +}; +//FIXME we should return a proper vcard, not an XMPP one +Lighstring.getVcard = function(aConnection, aTo, aCallback) { + aConnection.send(Mango.stanza.vcard.get(aTo), function(answer, err){ + if(answer) { + var vcard = answer.querySelector('vCard'); + if(vcard) + aCallback(vcard); + } + else + aCallback(null); + }); +} +// +//Disco +// +Lighstring.NS['disco#info'] = "http://jabber.org/protocol/disco#info"; +Lighstring.NS['disco#items'] = "http://jabber.org/protocol/disco#items"; +Lighstring.stanza.disco = { + items: function(aTo, aNode) { + if(aTo) + var iq = "<iq type='get' to='"+aTo+"'>"; + else + var iq = "<iq type='get'>"; + + if(aNode) + var query = "<query xmlns='"+Mango.NS['disco#items']+"' node='"+aNode+"'/>"; + else + var query = "<query xmlns='"+Mango.NS['disco#items']+"'/>"; + + return iq+query+"</iq>"; + }, + info: function(aTo, aNode) { + if(aTo) + var iq = "<iq type='get' to='"+aTo+"'>"; + else + var iq = "<iq type='get'>"; + if(aNode) + var query = "<query xmlns='"+Mango.NS['disco#info']+"' node='"+aNode+"'/>"; + else + var query = "<query xmlns='"+Mango.NS['disco#info']+"'/>"; + + return iq+query+"</iq>"; + } +}; +Lighstring.discoItems = function(aConnection, aTo, aCallback) { + aConnection.send(Mango.stanza.disco.items(aTo), function(answer){ + var items = []; + answer.querySelectorAll('item').forEach(function(node) { + var item = { + jid: node.getAttribute('jid'), + name: node.getAttribute('name'), + node: node.getAttribute('node') + } + items.push(item); + }); + if(aCallback) + aCallback(items); + }); +}; +Lighstring.discoInfo = function(aConnection, aTo, aNode, aCallback) { + aConnection.send(Mango.stanza.disco.info(aTo, aNode), function(answer){ + var field = answer.querySelector('field[var="pubsub#creator"] > value'); + var creator = field ? field.textContent : ''; + //FIXME callback the entire data + aCallback(creator); + }); +}; +// +//PubSub +// +Lighstring.NS.x = "jabber:x:data"; +Lighstring.NS.pubsub = "http://jabber.org/protocol/pubsub"; +Lighstring.NS.pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; +Lighstring.stanza.pubsub = { + getConfig: function(aTo, aNode) { + return "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><configure node='"+aNode+"'/></pubsub></iq>"; + }, + items: function(aTo, aNode) { + return "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><items node='"+aNode+"'/></pubsub></iq>"; + }, + affiliations: function(aTo, aNode) { + return "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><affiliations node='"+aNode+"'/></pubsub></iq>"; + }, + publish: function(aTo, aNode, aItem, aId) { + return "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><publish node='"+aNode+"'><item id='"+aId+"'>"+aItem+"</item></publish></pubsub></iq>"; + }, + retract: function(aTo, aNode, aItem) { + return "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><retract node='"+aNode+"'><item id='"+aItem+"'/></retract></pubsub></iq>"; + }, + 'delete': function(aTo, aNode, aURI) { + return "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><delete node='"+aNode+"'/></pubsub></iq>"; + }, + create: function(aTo, aNode, aFields) { + var iq = "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><create node='"+aNode+"'/>"; + if(aFields) { + iq += "<configure><x xmlns='"+Mango.NS.x+"' type='submit'>" + aFields.forEach(function(field) { + iq += field; + }); + iq += "</x></configure>"; + } + iq += "</pubsub></iq>"; + return iq; + }, + setAffiliations: function(aTo, aNode, aAffiliations) { + var iq = "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><affiliations node='"+aNode+"'>"; + for(var i = 0; i < aAffiliations.length; i++) { + iq += "<affiliation jid='"+aAffiliations[i][0]+"' affiliation='"+aAffiliations[i][1]+"'/>" + } + iq += "</affiliations></pubsub></iq>"; + return iq; + }, +}; +Lighstring.pubsubItems = function(aConnection, aTo, aNode, aCallback) { + aConnection.send(Mango.stanza.pubsub.items(aTo, aNode), function(answer){ + var items = []; + answer.querySelectorAll('item').forEach(function(node) { + var item = { + id: node.getAttribute('id'), + name: node.querySelector('title').textContent, + src: node.querySelector('content').getAttribute('src'), + type: node.querySelector('content').getAttribute('type'), + } + var thumbnail = node.querySelector('link'); + if(thumbnail) + item.thumbnail = thumbnail.getAttribute('href'); + items.push(item); + }) + if(aCallback) + aCallback(items); + }); +} +Lighstring.pubsubCreate = function(aConnection, aTo, aNode, aFields, aCallback) { + aConnection.send(Mango.stanza.pubsub.create(aTo, aNode, aFields), function(answer) { + if(answer.getAttribute('type') === 'result') + aCallback(null, answer); + else + aCallback(answer, null); + }); +}; +Lighstring.pubsubConfig = function(aConnection, aTo, aNode, aCallback) { + aConnection.send(Mango.stanza.pubsub.getConfig(aTo, aNode), function(answer){ + var accessmodel = answer.querySelector('field[var="pubsub#access_model"]').lastChild.textContent; + if(accessmodel) + aCallback(accessmodel); + else + aCallback(null); + }); +} +Lighstring.pubsubRetract = function(aConnection, aTo, aNode, aItem, aCallback) { + aConnection.send(Mango.stanza.pubsub.retract(aTo, aNode, aItem), function(answer){ + if(aCallback) + aCallback(answer); + }); +} +Lighstring.pubsubPublish = function(aConnection, aTo, aNode, aItem, aId, aCallback) { + aConnection.send(Mango.stanza.pubsub.publish(aTo, aNode, aItem, aId), function(answer){ + if(answer.getAttribute('type') === 'result') + aCallback(null, answer); + else + aCallback(answer, null); + }); +} +Lighstring.pubsubDelete = function(aConnection, aTo, aNode, aCallback) { + aConnection.send(Mango.stanza.pubsub.delete(aTo, aNode), function(answer){ + if(aCallback) + aCallback(answer); + }); +} +Lighstring.pubsubGetAffiliations = function(aConnection, aTo, aNode, aCallback) { + aConnection.send(Mango.stanza.pubsub.affiliations(aTo, aNode), function(answer) { + if((answer.getAttribute('type') === 'result') && aCallback) { + var affiliations = {}; + answer.querySelectorAll('affiliation').forEach(function(affiliation) { + affiliations[affiliation.getAttribute("jid")] = affiliation.getAttribute("affiliation"); + }) + aCallback(affiliations); + } + }); +}; +Lighstring.pubsubSetAffiliations = function(aConnection, aTo, aNode, aAffiliations, aCallback) { + aConnection.send(Mango.stanza.pubsub.setAffiliations(aTo, aNode, aAffiliations)); +}; +// +//IM +// +Lighstring.stanza.message = { + normal: function(aTo, aSubject, aText) { + return "<message type='normal' to='"+aTo+"'><subject>"+aSubject+"</subject><body>"+aText+"</body></message>"; + }, + chat: function(aTo, aText) { + return "<message type='chat' to='"+aTo+"'><body>"+aText+"</body></message>"; + } +}; +