Mercurial > eldonilo > barbecue
diff script2.js @ 5:03ef53b969bd
Add XMPP support.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 31 Jan 2012 01:09:41 +0100 |
parents | |
children | 24aa8dccb170 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/script2.js @@ -0,0 +1,354 @@ +'use strict'; + +/** Copyright (c) 2012 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +var conn = new Lightstring.Connection(SERVICE); +var roster = {}; +var documents = {}; + +Lightstring.NS.sxe = 'urn:xmpp:sxe:0'; +Lightstring.NS.jingle = { + main: 'urn:xmpp:jingle:1', + transports: { + sxe: 'urn:xmpp:jingle:transports:sxe' + }, + apps: { + xhtml: 'urn:xmpp:jingle:apps:xhtml' + } +}; + +conn.on('iq/' + Lightstring.NS['disco#info'] + ':query', function(stanza) { + if (stanza.DOM.getAttributeNS(null, 'type') !== 'get') + return; + + var query = stanza.DOM.firstChild; + if (query.getAttributeNS(null, 'node')) { + var response = "<iq to='" + stanza.DOM.getAttributeNS(null, 'from') + "'" + + " id='" + stanza.DOM.getAttributeNS(null, 'id') + "'" + + " type='error'/>"; //TODO: precise the error. + conn.send(response); + return; + } + + var features = [Lightstring.NS.sxe, Lightstring.NS.jingle.transports.sxe]; //TODO: put that elsewhere. + + var response = "<iq to='" + stanza.DOM.getAttributeNS(null, 'from') + "'" + + " id='" + stanza.DOM.getAttributeNS(null, 'id') + "'" + + " type='result'>" + + "<query xmlns='" + Lightstring.NS['disco#info'] + "'>" + + "<identity category='client' type='browser'/>"; + features.forEach(function(f) { + response += "<feature var='" + f + "'/>"; + }); + response += "</query>" + + "</iq>"; + + conn.send(response); +}); + +conn.on('presence', function(stanza) { + var from = new Lightstring.JID(stanza.DOM.getAttributeNS(null, 'from')); + if (!from.equals(conn.jid)) { + var type = stanza.DOM.getAttributeNS(null, 'type'); + if (!type) + Lightstring.discoInfo(conn, from, undefined, function(aData) { + roster[from.full] = aData; + }); + else if (type === 'unavailable') + delete roster[from.full]; + } +}); + +var host = function(sid, name) { + if (!sid) + sid = String(Math.random()); + + documents[sid] = new Document(conn.jid.full, name, conn.jid.full, 'document'); + + for (var jid in roster) { + var contact = roster[jid]; + if ((contact.features.indexOf(Lightstring.NS.sxe) !== -1) && (contact.features.indexOf(Lightstring.NS.jingle.transports.sxe) !== -1)) + initiate(jid, sid); + } +} + +var initiate = function(jid, sid) { + var initiate = "<iq to='" + jid + "' type='set'>" + + "<jingle xmlns='" + Lightstring.NS.jingle.main + "'" + + " action='session-initiate'" + + " initiator='" + documents[sid].initiator + "'" + + " sid='" + sid + "'>" + + "<content creator='initiator' name='" + documents[sid].name + "'>" + + "<description xmlns='" + Lightstring.NS.jingle.apps.xhtml + "'/>" + + "<transport xmlns='" + Lightstring.NS.jingle.transports.sxe + "'>" + + "<host>" + documents[sid].host + "</host>" + + "</transport>" + + "</content>" + + "</jingle>" + + "</iq>"; + conn.send(initiate); +}; + +var accept = function(sid) { + var accept = "<iq to='" + documents[sid].initiator + "' type='set'>" + + "<jingle xmlns='" + Lightstring.NS.jingle.main + "'" + + " action='session-accept'" + + " initiator='" + documents[sid].initiator + "'" + + " sid='" + sid + "'>" + + "<content creator='initiator' name='" + documents[sid].name + "'>" + + "<description xmlns='" + Lightstring.NS.jingle.apps.xhtml + "'/>" + + "<transport xmlns='" + Lightstring.NS.jingle.transports.sxe + "'>" + + "<host>" + documents[sid].host + "</host>" + + "</transport>" + + "</content>" + + "</jingle>" + + "</iq>"; + conn.send(accept, function(stanza) { + if (stanza.DOM.getAttributeNS(null, 'type') === 'result') + connect(sid); + else + terminate('TODO'); //XXX + }); +}; + +var terminate = function(sid, reason) { + if (!(sid in documents)) + return console.log('BIG WARNING!!!'); + + var terminate = "<iq to='" + documents[sid].initiator + "' type='set'>" + + "<jingle xmlns='" + Lightstring.NS.jingle.main + "'" + + " action='session-terminate'" + + " host='" + documents[sid].host + "'" + + " initiator='" + documents[sid].initiator + "'" + + " sid='" + sid + "'>" + + "<reason>" + + "<" + reason + "/>" + + "</reason>" + + "</jingle>" + + "</iq>"; + delete documents[sid]; + conn.send(terminate); +}; + +conn.on('iq/' + Lightstring.NS.jingle.main + ':jingle', function(stanza) { + conn.send("<iq to='" + stanza.DOM.getAttributeNS(null, 'from') + "'" + + " id='" + stanza.DOM.getAttributeNS(null, 'id') + "'" + + " type='result'/>"); + + var jingle = stanza.DOM.firstChild; + var action = jingle.getAttributeNS(null, 'action'); + var initiator = jingle.getAttributeNS(null, 'initiator'); + var sid = jingle.getAttributeNS(null, 'sid'); + + var payload = jingle.firstChild; //TODO: what if they are multiple? + + //TODO: verify that the payload is really what we want. + //TODO: use the reason even with content as payload. + if (action === 'session-initiate') { + var content = payload; + var creator = content.getAttributeNS(null, 'creator'); + var name = content.getAttributeNS(null, 'name'); + + if (documents[sid]) + return terminate(sid, 'alternative-session'); //TODO: The XEP: “and wishes to use that [previous] session instead” + + var description = content.getElementsByTagName('description')[0]; //TODO: supporte multiple applications. + if (description.namespaceURI !== Lightstring.NS.jingle.apps.xhtml) + return terminate(sid, 'unsupported-applications'); + + var transport = content.getElementsByTagName('transport')[0]; //TODO: supporte multiple transports. + if (transport.namespaceURI !== Lightstring.NS.jingle.transports.sxe) + return terminate(sid, 'unsupported-transports'); + + var host = transport.textContent; //TODO: verify the presence of the host element. + + documents[sid] = new Document(initiator, name, host, 'document'); + + if (confirm('Do you accept?')) + accept(sid); + else + terminate(sid, 'decline'); + + } else if (action === 'session-accept') { + var content = payload; + var creator = content.getAttributeNS(null, 'creator'); + var name = content.getAttributeNS(null, 'name'); + + alert('Accepted! \\o/'); + + var description = content.getElementsByTagName('description')[0]; //TODO: supporte multiple applications. + if (description.namespaceURI !== Lightstring.NS.jingle.apps.xhtml) + return terminate(sid, 'unsupported-applications'); + + var transport = content.getElementsByTagName('transport')[0]; //TODO: supporte multiple transports. + if (transport.namespaceURI !== Lightstring.NS.jingle.transports.sxe) + return terminate(sid, 'unsupported-transports'); + + var host = transport.textContent; //TODO: verify the presence of the host element. + + var doc = documents[sid]; + if (doc.initiator !== initiator || + doc.name !== name || + doc.host !== host) + return terminate(sid, 'unknown-error'); //XXX + + } else if (action === 'session-terminate') { + var reason = payload; + + //TODO: verify they are what the should be. + var element = reason.firstChild; + var text = element.nextSibling; + + alert('Terminated, reason: ' + element.localName + (text? ' (' + text.textContent + ')': '')); + + delete documents[sid]; + } +}); + +var connect = function(sid) { + var host = documents[sid].host; + var identities = roster[host].identities; + var type = 'chat'; + identities.forEach(function(identity) { + if (identity.category === 'conference') + type = 'groupchat'; + }); + var message = "<message to='" + host + "'" + + " type='" + type + "'>" + + "<sxe xmlns='" + Lightstring.NS.sxe + "'" + + " id='" + Lightstring.newId('sxe') + "'" + + " session='" + sid + "'>" + + "<connect/>" + + "</sxe>" + + "</message>"; + conn.send(message); +}; + +conn.on('message/' + Lightstring.NS.sxe + ':sxe', function(stanza) { + var from = stanza.DOM.getAttributeNS(null, 'from'); + var type = 'chat'; //TODO: always? + var sxe = stanza.DOM.firstChild; //TODO: there can be multiple payloads. + var id = sxe.getAttributeNS(null, 'id'); + var sid = sxe.getAttributeNS(null, 'session'); + + var doc = documents[sid]; + if (!doc) { + conn.send("<message to='" + from + "' type='error'/>"); //XXX + return; + } + + var payload = sxe.firstChild; //TODO: really? + switch (payload.localName) { + case 'connect': + var message = "<message to='" + from + "'" + + " type='" + type + "'>" + + "<sxe xmlns='" + Lightstring.NS.sxe + "'" + + " id='" + Lightstring.newId('sxe') + "'" + + " session='" + sid + "'>" + + "<state-offer>" + + "<description xmlns='" + Lightstring.NS.jingle.apps.xhtml + "'/>" + + "</state-offer>" + + "</sxe>" + + "</message>"; + conn.send(message); + break; + case 'state-offer': + var description = payload.firstChild; + if (description.namespaceURI !== Lightstring.NS.jingle.apps.xhtml) + return terminate(sid, 'unsupported-applications'); + + var accept = false; + if (from === doc.host || from === doc.initiator) + accept = true; + + //TODO: refuse if proposed multiple times. + + var message = "<message to='" + from + "'" + + " type='" + type + "'>" + + "<sxe xmlns='" + Lightstring.NS.sxe + "'" + + " id='" + Lightstring.newId('sxe') + "'" + + " session='" + sid + "'>" + + "<" + (accept? 'accept': 'refuse') + "-state/>" + + "</sxe>" + + "</message>"; + conn.send(message); + break; + case 'accept-state': + var message = "<message to='" + from + "'" + + " type='" + type + "'>" + + "<sxe xmlns='" + Lightstring.NS.sxe + "'" + + " id='" + Lightstring.newId('sxe') + "'" + + " session='" + sid + "'>" + + "<state>" + + "<document-begin prolog='" + doc.prolog + "'/>" + + //TODO: support non-empty documents. + "<document-end last-sender='' last-id=''/>" + + "</state>" + + "</sxe>" + + "</message>"; + conn.send(message); + break; + case 'refuse-state': + terminate(sid, 'decline'); + break; + case 'state': + var elements = payload.children; + doc.processState(from, elements); + break; + default: + var elements = sxe.children; + doc.processState(from, elements); + } +}); + +(function() { + var events = ['connected', 'conn-error', 'connecting', 'disconnected', 'disconnecting']; + var state = document.getElementById('state'); + events.forEach(function(e) { + conn.on(e, function() { + state.innerHTML = e; + }); + }); +})(); + +conn.on('error', function(a) { + alert(a); +}); + +document.getElementById('host').addEventListener('click', function() { + host(Lightstring.newId('sess'), 'My First XHTML Document'); +}, false); + +conn.on('connected', function() { + conn.on('output', function(stanza) { + console.log('out:', stanza.DOM); + }); + conn.on('input', function(stanza) { + console.log('in:', stanza.DOM); + }); + + Lightstring.presence(conn); +}); + +conn.connect(JID + '/' + prompt('Resource?'), PASSWORD); + +register_feature_not_implemented.call(conn);