Mercurial > eldonilo > barbecue
view script2.js @ 6:24aa8dccb170
Make XMPP actually work.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 31 Jan 2012 15:59:28 +0100 |
parents | 03ef53b969bd |
children | 853dcbe8f06f |
line wrap: on
line source
'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 doc = documents[sid]; doc.empty(); var accept = "<iq to='" + doc.initiator + "' type='set'>" + "<jingle xmlns='" + Lightstring.NS.jingle.main + "'" + " action='session-accept'" + " initiator='" + doc.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>" + doc.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 initialState = doc.createState([]).map(function(element) { return Lightstring.DOM2XML(element); }).join(''); var message = "<message to='" + from + "'" + " type='" + type + "'>" + "<sxe xmlns='" + Lightstring.NS.sxe + "'" + " id='" + Lightstring.newId('sxe') + "'" + " session='" + sid + "'>" + "<state>" + "<document-begin prolog='" + doc.prolog + "'/>" + initialState + "<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);