summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--changelog.d/12909.removal1
-rw-r--r--contrib/jitsimeetbridge/jitsimeetbridge.py298
-rw-r--r--contrib/jitsimeetbridge/syweb-jitsi-conference.patch188
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.js712
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.util.js408
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe/XMLHttpRequest.js254
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe/base64.js83
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe/md5.js279
-rw-r--r--contrib/jitsimeetbridge/unjingle/strophe/strophe.js3256
-rw-r--r--contrib/jitsimeetbridge/unjingle/unjingle.js48
-rw-r--r--debian/changelog7
-rw-r--r--debian/copyright23
12 files changed, 8 insertions, 5549 deletions
diff --git a/changelog.d/12909.removal b/changelog.d/12909.removal
new file mode 100644
index 0000000000..0baff46ea9
--- /dev/null
+++ b/changelog.d/12909.removal
@@ -0,0 +1 @@
+Remove `contrib/jitsimeetbridge`. This was an unused experiment that hasn't been meaningfully changed since 2014.
diff --git a/contrib/jitsimeetbridge/jitsimeetbridge.py b/contrib/jitsimeetbridge/jitsimeetbridge.py
deleted file mode 100644
index b3de468687..0000000000
--- a/contrib/jitsimeetbridge/jitsimeetbridge.py
+++ /dev/null
@@ -1,298 +0,0 @@
-#!/usr/bin/env python
-
-"""
-This is an attempt at bridging matrix clients into a Jitis meet room via Matrix
-video call.  It uses hard-coded xml strings overg XMPP BOSH. It can display one
-of the streams from the Jitsi bridge until the second lot of SDP comes down and
-we set the remote SDP at which point the stream ends. Our video never gets to
-the bridge.
-
-Requires:
-npm install jquery jsdom
-"""
-import json
-import subprocess
-import time
-
-import gevent
-import grequests
-from BeautifulSoup import BeautifulSoup
-
-ACCESS_TOKEN = ""
-
-MATRIXBASE = "https://matrix.org/_matrix/client/api/v1/"
-MYUSERNAME = "@davetest:matrix.org"
-
-HTTPBIND = "https://meet.jit.si/http-bind"
-# HTTPBIND = 'https://jitsi.vuc.me/http-bind'
-# ROOMNAME = "matrix"
-ROOMNAME = "pibble"
-
-HOST = "guest.jit.si"
-# HOST="jitsi.vuc.me"
-
-TURNSERVER = "turn.guest.jit.si"
-# TURNSERVER="turn.jitsi.vuc.me"
-
-ROOMDOMAIN = "meet.jit.si"
-# ROOMDOMAIN="conference.jitsi.vuc.me"
-
-
-class TrivialMatrixClient:
-    def __init__(self, access_token):
-        self.token = None
-        self.access_token = access_token
-
-    def getEvent(self):
-        while True:
-            url = (
-                MATRIXBASE
-                + "events?access_token="
-                + self.access_token
-                + "&timeout=60000"
-            )
-            if self.token:
-                url += "&from=" + self.token
-            req = grequests.get(url)
-            resps = grequests.map([req])
-            obj = json.loads(resps[0].content)
-            print("incoming from matrix", obj)
-            if "end" not in obj:
-                continue
-            self.token = obj["end"]
-            if len(obj["chunk"]):
-                return obj["chunk"][0]
-
-    def joinRoom(self, roomId):
-        url = MATRIXBASE + "rooms/" + roomId + "/join?access_token=" + self.access_token
-        print(url)
-        headers = {"Content-Type": "application/json"}
-        req = grequests.post(url, headers=headers, data="{}")
-        resps = grequests.map([req])
-        obj = json.loads(resps[0].content)
-        print("response: ", obj)
-
-    def sendEvent(self, roomId, evType, event):
-        url = (
-            MATRIXBASE
-            + "rooms/"
-            + roomId
-            + "/send/"
-            + evType
-            + "?access_token="
-            + self.access_token
-        )
-        print(url)
-        print(json.dumps(event))
-        headers = {"Content-Type": "application/json"}
-        req = grequests.post(url, headers=headers, data=json.dumps(event))
-        resps = grequests.map([req])
-        obj = json.loads(resps[0].content)
-        print("response: ", obj)
-
-
-xmppClients = {}
-
-
-def matrixLoop():
-    while True:
-        ev = matrixCli.getEvent()
-        print(ev)
-        if ev["type"] == "m.room.member":
-            print("membership event")
-            if ev["membership"] == "invite" and ev["state_key"] == MYUSERNAME:
-                roomId = ev["room_id"]
-                print("joining room %s" % (roomId))
-                matrixCli.joinRoom(roomId)
-        elif ev["type"] == "m.room.message":
-            if ev["room_id"] in xmppClients:
-                print("already have a bridge for that user, ignoring")
-                continue
-            print("got message, connecting")
-            xmppClients[ev["room_id"]] = TrivialXmppClient(ev["room_id"], ev["user_id"])
-            gevent.spawn(xmppClients[ev["room_id"]].xmppLoop)
-        elif ev["type"] == "m.call.invite":
-            print("Incoming call")
-            # sdp = ev['content']['offer']['sdp']
-            # print "sdp: %s" % (sdp)
-            # xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
-            # gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
-        elif ev["type"] == "m.call.answer":
-            print("Call answered")
-            sdp = ev["content"]["answer"]["sdp"]
-            if ev["room_id"] not in xmppClients:
-                print("We didn't have a call for that room")
-                continue
-            # should probably check call ID too
-            xmppCli = xmppClients[ev["room_id"]]
-            xmppCli.sendAnswer(sdp)
-        elif ev["type"] == "m.call.hangup":
-            if ev["room_id"] in xmppClients:
-                xmppClients[ev["room_id"]].stop()
-                del xmppClients[ev["room_id"]]
-
-
-class TrivialXmppClient:
-    def __init__(self, matrixRoom, userId):
-        self.rid = 0
-        self.matrixRoom = matrixRoom
-        self.userId = userId
-        self.running = True
-
-    def stop(self):
-        self.running = False
-
-    def nextRid(self):
-        self.rid += 1
-        return "%d" % (self.rid)
-
-    def sendIq(self, xml):
-        fullXml = (
-            "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s'>%s</body>"
-            % (self.nextRid(), self.sid, xml)
-        )
-        # print "\t>>>%s" % (fullXml)
-        return self.xmppPoke(fullXml)
-
-    def xmppPoke(self, xml):
-        headers = {"Content-Type": "application/xml"}
-        req = grequests.post(HTTPBIND, verify=False, headers=headers, data=xml)
-        resps = grequests.map([req])
-        obj = BeautifulSoup(resps[0].content)
-        return obj
-
-    def sendAnswer(self, answer):
-        print("sdp from matrix client", answer)
-        p = subprocess.Popen(
-            ["node", "unjingle/unjingle.js", "--sdp"],
-            stdin=subprocess.PIPE,
-            stdout=subprocess.PIPE,
-        )
-        jingle, out_err = p.communicate(answer)
-        jingle = jingle % {
-            "tojid": self.callfrom,
-            "action": "session-accept",
-            "initiator": self.callfrom,
-            "responder": self.jid,
-            "sid": self.callsid,
-        }
-        print("answer jingle from sdp", jingle)
-        res = self.sendIq(jingle)
-        print("reply from answer: ", res)
-
-        self.ssrcs = {}
-        jingleSoup = BeautifulSoup(jingle)
-        for cont in jingleSoup.iq.jingle.findAll("content"):
-            if cont.description:
-                self.ssrcs[cont["name"]] = cont.description["ssrc"]
-        print("my ssrcs:", self.ssrcs)
-
-        gevent.joinall([gevent.spawn(self.advertiseSsrcs)])
-
-    def advertiseSsrcs(self):
-        time.sleep(7)
-        print("SSRC spammer started")
-        while self.running:
-            ssrcMsg = (
-                "<presence to='%(tojid)s' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%(nick)s</nick><stats xmlns='http://jitsi.org/jitmeet/stats'><stat name='bitrate_download' value='175'/><stat name='bitrate_upload' value='176'/><stat name='packetLoss_total' value='0'/><stat name='packetLoss_download' value='0'/><stat name='packetLoss_upload' value='0'/></stats><media xmlns='http://estos.de/ns/mjs'><source type='audio' ssrc='%(assrc)s' direction='sendre'/><source type='video' ssrc='%(vssrc)s' direction='sendre'/></media></presence>"
-                % {
-                    "tojid": "%s@%s/%s" % (ROOMNAME, ROOMDOMAIN, self.shortJid),
-                    "nick": self.userId,
-                    "assrc": self.ssrcs["audio"],
-                    "vssrc": self.ssrcs["video"],
-                }
-            )
-            res = self.sendIq(ssrcMsg)
-            print("reply from ssrc announce: ", res)
-            time.sleep(10)
-
-    def xmppLoop(self):
-        self.matrixCallId = time.time()
-        res = self.xmppPoke(
-            "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' to='%s' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>"
-            % (self.nextRid(), HOST)
-        )
-
-        print(res)
-        self.sid = res.body["sid"]
-        print("sid %s" % (self.sid))
-
-        res = self.sendIq(
-            "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>"
-        )
-
-        res = self.xmppPoke(
-            "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s' to='%s' xml:lang='en' xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'/>"
-            % (self.nextRid(), self.sid, HOST)
-        )
-
-        res = self.sendIq(
-            "<iq type='set' id='_bind_auth_2' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>"
-        )
-        print(res)
-
-        self.jid = res.body.iq.bind.jid.string
-        print("jid: %s" % (self.jid))
-        self.shortJid = self.jid.split("-")[0]
-
-        res = self.sendIq(
-            "<iq type='set' id='_session_auth_2' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>"
-        )
-
-        # randomthing = res.body.iq['to']
-        # whatsitpart = randomthing.split('-')[0]
-
-        # print "other random bind thing: %s" % (randomthing)
-
-        # advertise preence to the jitsi room, with our nick
-        res = self.sendIq(
-            "<iq type='get' to='%s' xmlns='jabber:client' id='1:sendIQ'><services xmlns='urn:xmpp:extdisco:1'><service host='%s'/></services></iq><presence to='%s@%s/d98f6c40' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%s</nick></presence>"
-            % (HOST, TURNSERVER, ROOMNAME, ROOMDOMAIN, self.userId)
-        )
-        self.muc = {"users": []}
-        for p in res.body.findAll("presence"):
-            u = {}
-            u["shortJid"] = p["from"].split("/")[1]
-            if p.c and p.c.nick:
-                u["nick"] = p.c.nick.string
-            self.muc["users"].append(u)
-        print("muc: ", self.muc)
-
-        # wait for stuff
-        while True:
-            print("waiting...")
-            res = self.sendIq("")
-            print("got from stream: ", res)
-            if res.body.iq:
-                jingles = res.body.iq.findAll("jingle")
-                if len(jingles):
-                    self.callfrom = res.body.iq["from"]
-                    self.handleInvite(jingles[0])
-            elif "type" in res.body and res.body["type"] == "terminate":
-                self.running = False
-                del xmppClients[self.matrixRoom]
-                return
-
-    def handleInvite(self, jingle):
-        self.initiator = jingle["initiator"]
-        self.callsid = jingle["sid"]
-        p = subprocess.Popen(
-            ["node", "unjingle/unjingle.js", "--jingle"],
-            stdin=subprocess.PIPE,
-            stdout=subprocess.PIPE,
-        )
-        print("raw jingle invite", str(jingle))
-        sdp, out_err = p.communicate(str(jingle))
-        print("transformed remote offer sdp", sdp)
-        inviteEvent = {
-            "offer": {"type": "offer", "sdp": sdp},
-            "call_id": self.matrixCallId,
-            "version": 0,
-            "lifetime": 30000,
-        }
-        matrixCli.sendEvent(self.matrixRoom, "m.call.invite", inviteEvent)
-
-
-matrixCli = TrivialMatrixClient(ACCESS_TOKEN)  # Undefined name
-
-gevent.joinall([gevent.spawn(matrixLoop)])
diff --git a/contrib/jitsimeetbridge/syweb-jitsi-conference.patch b/contrib/jitsimeetbridge/syweb-jitsi-conference.patch
deleted file mode 100644
index aed23c78aa..0000000000
--- a/contrib/jitsimeetbridge/syweb-jitsi-conference.patch
+++ /dev/null
@@ -1,188 +0,0 @@
-diff --git a/syweb/webclient/app/components/matrix/matrix-call.js b/syweb/webclient/app/components/matrix/matrix-call.js
-index 9fbfff0..dc68077 100644
---- a/syweb/webclient/app/components/matrix/matrix-call.js
-+++ b/syweb/webclient/app/components/matrix/matrix-call.js
-@@ -16,6 +16,45 @@ limitations under the License.
- 
- 'use strict';
- 
-+
-+function sendKeyframe(pc) {
-+    console.log('sendkeyframe', pc.iceConnectionState);
-+    if (pc.iceConnectionState !== 'connected') return; // safe...
-+    pc.setRemoteDescription(
-+        pc.remoteDescription,
-+        function () {
-+            pc.createAnswer(
-+                function (modifiedAnswer) {
-+                    pc.setLocalDescription(
-+                        modifiedAnswer,
-+                        function () {
-+                            // noop
-+                        },
-+                        function (error) {
-+                            console.log('triggerKeyframe setLocalDescription failed', error);
-+                            messageHandler.showError();
-+                        }
-+                    );
-+                },
-+                function (error) {
-+                    console.log('triggerKeyframe createAnswer failed', error);
-+                    messageHandler.showError();
-+                }
-+            );
-+        },
-+        function (error) {
-+            console.log('triggerKeyframe setRemoteDescription failed', error);
-+            messageHandler.showError();
-+        }
-+    );
-+} 
-+
-+
-+
-+
-+
-+
-+
- var forAllVideoTracksOnStream = function(s, f) {
-     var tracks = s.getVideoTracks();
-     for (var i = 0; i < tracks.length; i++) {
-@@ -83,7 +122,7 @@ angular.module('MatrixCall', [])
-     }
- 
-     // FIXME: we should prevent any calls from being placed or accepted before this has finished
--    MatrixCall.getTurnServer();
-+    //MatrixCall.getTurnServer();
- 
-     MatrixCall.CALL_TIMEOUT = 60000;
-     MatrixCall.FALLBACK_STUN_SERVER = 'stun:stun.l.google.com:19302';
-@@ -132,6 +171,22 @@ angular.module('MatrixCall', [])
-         pc.onsignalingstatechange = function() { self.onSignallingStateChanged(); };
-         pc.onicecandidate = function(c) { self.gotLocalIceCandidate(c); };
-         pc.onaddstream = function(s) { self.onAddStream(s); };
-+
-+        var datachan = pc.createDataChannel('RTCDataChannel', {
-+            reliable: false
-+        });
-+        console.log("data chan: "+datachan);
-+        datachan.onopen = function() {
-+            console.log("data channel open");
-+        };
-+        datachan.onmessage = function() {
-+            console.log("data channel message");
-+        };
-+        pc.ondatachannel = function(event) {
-+            console.log("have data channel");
-+            event.channel.binaryType = 'blob';
-+        };
-+
-         return pc;
-     }
- 
-@@ -200,6 +255,12 @@ angular.module('MatrixCall', [])
-         }, this.msg.lifetime - event.age);
-     };
- 
-+    MatrixCall.prototype.receivedInvite = function(event) {
-+        console.log("Got second invite for call "+this.call_id);
-+        this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError);
-+    };
-+    
-+
-     // perverse as it may seem, sometimes we want to instantiate a call with a hangup message
-     // (because when getting the state of the room on load, events come in reverse order and
-     // we want to remember that a call has been hung up)
-@@ -349,7 +410,7 @@ angular.module('MatrixCall', [])
-             'mandatory': {
-                 'OfferToReceiveAudio': true,
-                 'OfferToReceiveVideo': this.type == 'video'
--            },
-+            }
-         };
-         this.peerConn.createAnswer(function(d) { self.createdAnswer(d); }, function(e) {}, constraints);
-         // This can't be in an apply() because it's called by a predecessor call under glare conditions :(
-@@ -359,8 +420,20 @@ angular.module('MatrixCall', [])
-     MatrixCall.prototype.gotLocalIceCandidate = function(event) {
-         if (event.candidate) {
-             console.log("Got local ICE "+event.candidate.sdpMid+" candidate: "+event.candidate.candidate);
--            this.sendCandidate(event.candidate);
--        }
-+            //this.sendCandidate(event.candidate);
-+        } else {
-+            console.log("have all candidates, sending answer");
-+            var content = {
-+                version: 0,
-+                call_id: this.call_id,
-+                answer: this.peerConn.localDescription
-+            };
-+            this.sendEventWithRetry('m.call.answer', content);
-+            var self = this;
-+            $rootScope.$apply(function() {
-+                self.state = 'connecting';
-+            });
-+	}
-     }
- 
-     MatrixCall.prototype.gotRemoteIceCandidate = function(cand) {
-@@ -418,15 +491,6 @@ angular.module('MatrixCall', [])
-         console.log("Created answer: "+description);
-         var self = this;
-         this.peerConn.setLocalDescription(description, function() {
--            var content = {
--                version: 0,
--                call_id: self.call_id,
--                answer: self.peerConn.localDescription
--            };
--            self.sendEventWithRetry('m.call.answer', content);
--            $rootScope.$apply(function() {
--                self.state = 'connecting';
--            });
-         }, function() { console.log("Error setting local description!"); } );
-     };
- 
-@@ -448,6 +512,9 @@ angular.module('MatrixCall', [])
-             $rootScope.$apply(function() {
-                 self.state = 'connected';
-                 self.didConnect = true;
-+		/*$timeout(function() {
-+                    sendKeyframe(self.peerConn);
-+                }, 1000);*/
-             });
-         } else if (this.peerConn.iceConnectionState == 'failed') {
-             this.hangup('ice_failed');
-@@ -518,6 +585,7 @@ angular.module('MatrixCall', [])
- 
-     MatrixCall.prototype.onRemoteStreamEnded = function(event) {
-         console.log("Remote stream ended");
-+        return;
-         var self = this;
-         $rootScope.$apply(function() {
-             self.state = 'ended';
-diff --git a/syweb/webclient/app/components/matrix/matrix-phone-service.js b/syweb/webclient/app/components/matrix/matrix-phone-service.js
-index 55dbbf5..272fa27 100644
---- a/syweb/webclient/app/components/matrix/matrix-phone-service.js
-+++ b/syweb/webclient/app/components/matrix/matrix-phone-service.js
-@@ -48,6 +48,13 @@ angular.module('matrixPhoneService', [])
-                 return;
-             }
- 
-+            // do we already have an entry for this call ID?
-+            var existingEntry = matrixPhoneService.allCalls[msg.call_id];
-+            if (existingEntry) {
-+                 existingEntry.receivedInvite(msg);
-+                 return;
-+            }
-+
-             var call = undefined;
-             if (!isLive) {
-                 // if this event wasn't live then this call may already be over
-@@ -108,7 +115,7 @@ angular.module('matrixPhoneService', [])
-                     call.hangup();
-                 }
-             } else {
--                $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
-+                 $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
-             }
-         } else if (event.type == 'm.call.answer') {
-             var call = matrixPhoneService.allCalls[msg.call_id];
diff --git a/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.js b/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.js
deleted file mode 100644
index e99dd7bf96..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.js
+++ /dev/null
@@ -1,712 +0,0 @@
-/* jshint -W117 */
-// SDP STUFF
-function SDP(sdp) {
-    this.media = sdp.split('\r\nm=');
-    for (var i = 1; i < this.media.length; i++) {
-        this.media[i] = 'm=' + this.media[i];
-        if (i != this.media.length - 1) {
-            this.media[i] += '\r\n';
-        }
-    }
-    this.session = this.media.shift() + '\r\n';
-    this.raw = this.session + this.media.join('');
-}
-
-exports.SDP = SDP;
-
-var jsdom          = require("jsdom");
-var window = jsdom.jsdom().parentWindow;
-var $ = require('jquery')(window);
-
-var SDPUtil = require('./strophe.jingle.sdp.util.js').SDPUtil;
-
-/**
- * Returns map of MediaChannel mapped per channel idx.
- */
-SDP.prototype.getMediaSsrcMap = function() {
-    var self = this;
-    var media_ssrcs = {};
-    for (channelNum = 0; channelNum < self.media.length; channelNum++) {
-        modified = true;
-        tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:');
-        var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:'));
-        var channel = new MediaChannel(channelNum, type);
-        media_ssrcs[channelNum] = channel;
-        tmp.forEach(function (line) {
-            var linessrc = line.substring(7).split(' ')[0];
-            // allocate new ChannelSsrc
-            if(!channel.ssrcs[linessrc]) {
-                channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type);
-            }
-            channel.ssrcs[linessrc].lines.push(line);
-        });
-        tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc-group:');
-        tmp.forEach(function(line){
-            var semantics = line.substr(0, idx).substr(13);
-            var ssrcs = line.substr(14 + semantics.length).split(' ');
-            if (ssrcs.length != 0) {
-                var ssrcGroup = new ChannelSsrcGroup(semantics, ssrcs);
-                channel.ssrcGroups.push(ssrcGroup);
-            }
-        });
-    }
-    return media_ssrcs;
-};
-/**
- * Returns <tt>true</tt> if this SDP contains given SSRC.
- * @param ssrc the ssrc to check.
- * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
- */
-SDP.prototype.containsSSRC = function(ssrc) {
-    var channels = this.getMediaSsrcMap();
-    var contains = false;
-    Object.keys(channels).forEach(function(chNumber){
-        var channel = channels[chNumber];
-        //console.log("Check", channel, ssrc);
-        if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){
-            contains = true;
-        }
-    });
-    return contains;
-};
-
-/**
- * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
- * @param otherSdp the other SDP to check ssrc with.
- */
-SDP.prototype.getNewMedia = function(otherSdp) {
-
-    // this could be useful in Array.prototype.
-    function arrayEquals(array) {
-        // if the other array is a falsy value, return
-        if (!array)
-            return false;
-
-        // compare lengths - can save a lot of time
-        if (this.length != array.length)
-            return false;
-
-        for (var i = 0, l=this.length; i < l; i++) {
-            // Check if we have nested arrays
-            if (this[i] instanceof Array && array[i] instanceof Array) {
-                // recurse into the nested arrays
-                if (!this[i].equals(array[i]))
-                    return false;
-            }
-            else if (this[i] != array[i]) {
-                // Warning - two different object instances will never be equal: {x:20} != {x:20}
-                return false;
-            }
-        }
-        return true;
-    }
-
-    var myMedia = this.getMediaSsrcMap();
-    var othersMedia = otherSdp.getMediaSsrcMap();
-    var newMedia = {};
-    Object.keys(othersMedia).forEach(function(channelNum) {
-        var myChannel = myMedia[channelNum];
-        var othersChannel = othersMedia[channelNum];
-        if(!myChannel && othersChannel) {
-            // Add whole channel
-            newMedia[channelNum] = othersChannel;
-            return;
-        }
-        // Look for new ssrcs accross the channel
-        Object.keys(othersChannel.ssrcs).forEach(function(ssrc) {
-            if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) {
-                // Allocate channel if we've found ssrc that doesn't exist in our channel
-                if(!newMedia[channelNum]){
-                    newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
-                }
-                newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
-            }
-        });
-
-        // Look for new ssrc groups across the channels
-        othersChannel.ssrcGroups.forEach(function(otherSsrcGroup){
-
-            // try to match the other ssrc-group with an ssrc-group of ours
-            var matched = false;
-            for (var i = 0; i < myChannel.ssrcGroups.length; i++) {
-                var mySsrcGroup = myChannel.ssrcGroups[i];
-                if (otherSsrcGroup.semantics == mySsrcGroup.semantics
-                    && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
-
-                    matched = true;
-                    break;
-                }
-            }
-
-            if (!matched) {
-                // Allocate channel if we've found an ssrc-group that doesn't
-                // exist in our channel
-
-                if(!newMedia[channelNum]){
-                    newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
-                }
-                newMedia[channelNum].ssrcGroups.push(otherSsrcGroup);
-            }
-        });
-    });
-    return newMedia;
-};
-
-// remove iSAC and CN from SDP
-SDP.prototype.mangle = function () {
-    var i, j, mline, lines, rtpmap, newdesc;
-    for (i = 0; i < this.media.length; i++) {
-        lines = this.media[i].split('\r\n');
-        lines.pop(); // remove empty last element
-        mline = SDPUtil.parse_mline(lines.shift());
-        if (mline.media != 'audio')
-            continue;
-        newdesc = '';
-        mline.fmt.length = 0;
-        for (j = 0; j < lines.length; j++) {
-            if (lines[j].substr(0, 9) == 'a=rtpmap:') {
-                rtpmap = SDPUtil.parse_rtpmap(lines[j]);
-                if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
-                    continue;
-                mline.fmt.push(rtpmap.id);
-                newdesc += lines[j] + '\r\n';
-            } else {
-                newdesc += lines[j] + '\r\n';
-            }
-        }
-        this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
-        this.media[i] += newdesc;
-    }
-    this.raw = this.session + this.media.join('');
-};
-
-// remove lines matching prefix from session section
-SDP.prototype.removeSessionLines = function(prefix) {
-    var self = this;
-    var lines = SDPUtil.find_lines(this.session, prefix);
-    lines.forEach(function(line) {
-        self.session = self.session.replace(line + '\r\n', '');
-    });
-    this.raw = this.session + this.media.join('');
-    return lines;
-}
-// remove lines matching prefix from a media section specified by mediaindex
-// TODO: non-numeric mediaindex could match mid
-SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
-    var self = this;
-    var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
-    lines.forEach(function(line) {
-        self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
-    });
-    this.raw = this.session + this.media.join('');
-    return lines;
-}
-
-// add content's to a jingle element
-SDP.prototype.toJingle = function (elem, thecreator) {
-    var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
-    var self = this;
-    // new bundle plan
-    if (SDPUtil.find_line(this.session, 'a=group:')) {
-        lines = SDPUtil.find_lines(this.session, 'a=group:');
-        for (i = 0; i < lines.length; i++) {
-            tmp = lines[i].split(' ');
-            var semantics = tmp.shift().substr(8);
-            elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
-            for (j = 0; j < tmp.length; j++) {
-                elem.c('content', {name: tmp[j]}).up();
-            }
-            elem.up();
-        }
-    }
-    // old bundle plan, to be removed
-    var bundle = [];
-    if (SDPUtil.find_line(this.session, 'a=group:BUNDLE')) {
-        bundle = SDPUtil.find_line(this.session, 'a=group:BUNDLE ').split(' ');
-        bundle.shift();
-    }
-    for (i = 0; i < this.media.length; i++) {
-        mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
-        if (!(mline.media === 'audio' ||
-              mline.media === 'video' ||
-              mline.media === 'application'))
-        {
-            continue;
-        }
-        if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
-            ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
-        } else {
-            ssrc = false;
-        }
-
-        elem.c('content', {creator: thecreator, name: mline.media});
-        if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
-            // prefer identifier from a=mid if present
-            var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
-            elem.attrs({ name: mid });
-
-            // old BUNDLE plan, to be removed
-            if (bundle.indexOf(mid) !== -1) {
-                elem.c('bundle', {xmlns: 'http://estos.de/ns/bundle'}).up();
-                bundle.splice(bundle.indexOf(mid), 1);
-            }
-        }
-
-        if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
-        {
-            elem.c('description',
-                {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
-                    media: mline.media });
-            if (ssrc) {
-                elem.attrs({ssrc: ssrc});
-            }
-            for (j = 0; j < mline.fmt.length; j++) {
-                rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
-                elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
-                // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
-                if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
-                    tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
-                    for (k = 0; k < tmp.length; k++) {
-                        elem.c('parameter', tmp[k]).up();
-                    }
-                }
-                this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
-
-                elem.up();
-            }
-            if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
-                elem.c('encryption', {required: 1});
-                var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
-                crypto.forEach(function(line) {
-                    elem.c('crypto', SDPUtil.parse_crypto(line)).up();
-                });
-                elem.up(); // end of encryption
-            }
-
-            if (ssrc) {
-                // new style mapping
-                elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
-                // FIXME: group by ssrc and support multiple different ssrcs
-                var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
-                ssrclines.forEach(function(line) {
-                    idx = line.indexOf(' ');
-                    var linessrc = line.substr(0, idx).substr(7);
-                    if (linessrc != ssrc) {
-                        elem.up();
-                        ssrc = linessrc;
-                        elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
-                    }
-                    var kv = line.substr(idx + 1);
-                    elem.c('parameter');
-                    if (kv.indexOf(':') == -1) {
-                        elem.attrs({ name: kv });
-                    } else {
-                        elem.attrs({ name: kv.split(':', 2)[0] });
-                        elem.attrs({ value: kv.split(':', 2)[1] });
-                    }
-                    elem.up();
-                });
-                elem.up();
-
-                // old proprietary mapping, to be removed at some point
-                tmp = SDPUtil.parse_ssrc(this.media[i]);
-                tmp.xmlns = 'http://estos.de/ns/ssrc';
-                tmp.ssrc = ssrc;
-                elem.c('ssrc', tmp).up(); // ssrc is part of description
-
-                // XEP-0339 handle ssrc-group attributes
-                var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
-                ssrc_group_lines.forEach(function(line) {
-                    idx = line.indexOf(' ');
-                    var semantics = line.substr(0, idx).substr(13);
-                    var ssrcs = line.substr(14 + semantics.length).split(' ');
-                    if (ssrcs.length != 0) {
-                        elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
-                        ssrcs.forEach(function(ssrc) {
-                            elem.c('source', { ssrc: ssrc })
-                                .up();
-                        });
-                        elem.up();
-                    }
-                });
-            }
-
-            if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
-                elem.c('rtcp-mux').up();
-            }
-
-            // XEP-0293 -- map a=rtcp-fb:*
-            this.RtcpFbToJingle(i, elem, '*');
-
-            // XEP-0294
-            if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
-                lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
-                for (j = 0; j < lines.length; j++) {
-                    tmp = SDPUtil.parse_extmap(lines[j]);
-                    elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
-                        uri: tmp.uri,
-                        id: tmp.value });
-                    if (tmp.hasOwnProperty('direction')) {
-                        switch (tmp.direction) {
-                            case 'sendonly':
-                                elem.attrs({senders: 'responder'});
-                                break;
-                            case 'recvonly':
-                                elem.attrs({senders: 'initiator'});
-                                break;
-                            case 'sendrecv':
-                                elem.attrs({senders: 'both'});
-                                break;
-                            case 'inactive':
-                                elem.attrs({senders: 'none'});
-                                break;
-                        }
-                    }
-                    // TODO: handle params
-                    elem.up();
-                }
-            }
-            elem.up(); // end of description
-        }
-
-        // map ice-ufrag/pwd, dtls fingerprint, candidates
-        this.TransportToJingle(i, elem);
-
-        if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
-            elem.attrs({senders: 'both'});
-        } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
-            elem.attrs({senders: 'initiator'});
-        } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
-            elem.attrs({senders: 'responder'});
-        } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
-            elem.attrs({senders: 'none'});
-        }
-        if (mline.port == '0') {
-            // estos hack to reject an m-line
-            elem.attrs({senders: 'rejected'});
-        }
-        elem.up(); // end of content
-    }
-    elem.up();
-    return elem;
-};
-
-SDP.prototype.TransportToJingle = function (mediaindex, elem) {
-    var i = mediaindex;
-    var tmp;
-    var self = this;
-    elem.c('transport');
-
-    // XEP-0343 DTLS/SCTP
-    if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
-    {
-        var sctpmap = SDPUtil.find_line(
-            this.media[i], 'a=sctpmap:', self.session);
-        if (sctpmap)
-        {
-            var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
-            elem.c('sctpmap',
-                {
-                    xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
-                    number: sctpAttrs[0], /* SCTP port */
-                    protocol: sctpAttrs[1], /* protocol */
-                });
-            // Optional stream count attribute
-            if (sctpAttrs.length > 2)
-                elem.attrs({ streams: sctpAttrs[2]});
-            elem.up();
-        }
-    }
-    // XEP-0320
-    var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
-    fingerprints.forEach(function(line) {
-        tmp = SDPUtil.parse_fingerprint(line);
-        tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
-        elem.c('fingerprint').t(tmp.fingerprint);
-        delete tmp.fingerprint;
-        line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
-        if (line) {
-            tmp.setup = line.substr(8);
-        }
-        elem.attrs(tmp);
-        elem.up(); // end of fingerprint
-    });
-    tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
-    if (tmp) {
-        tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
-        elem.attrs(tmp);
-        // XEP-0176
-        if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
-            var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
-            lines.forEach(function (line) {
-                elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
-            });
-        }
-    }
-    elem.up(); // end of transport
-}
-
-SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
-    var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
-    lines.forEach(function (line) {
-        var tmp = SDPUtil.parse_rtcpfb(line);
-        if (tmp.type == 'trr-int') {
-            elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
-            elem.up();
-        } else {
-            elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
-            if (tmp.params.length > 0) {
-                elem.attrs({'subtype': tmp.params[0]});
-            }
-            elem.up();
-        }
-    });
-};
-
-SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
-    var media = '';
-    var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
-    if (tmp.length) {
-        media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
-        if (tmp.attr('value')) {
-            media += tmp.attr('value');
-        } else {
-            media += '0';
-        }
-        media += '\r\n';
-    }
-    tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
-    tmp.each(function () {
-        media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
-        if ($(this).attr('subtype')) {
-            media += ' ' + $(this).attr('subtype');
-        }
-        media += '\r\n';
-    });
-    return media;
-};
-
-// construct an SDP from a jingle stanza
-SDP.prototype.fromJingle = function (jingle) {
-    var self = this;
-    this.raw = 'v=0\r\n' +
-        'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
-        's=-\r\n' +
-        't=0 0\r\n';
-    // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
-    if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
-        $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
-            var contents = $(group).find('>content').map(function (idx, content) {
-                return content.getAttribute('name');
-            }).get();
-            if (contents.length > 0) {
-                self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
-            }
-        });
-    } else if ($(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').length) {
-        // temporary namespace, not to be used. to be removed soon.
-        $(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').each(function (idx, group) {
-            var contents = $(group).find('>content').map(function (idx, content) {
-                return content.getAttribute('name');
-            }).get();
-            if (group.getAttribute('type') !== null && contents.length > 0) {
-                self.raw += 'a=group:' + group.getAttribute('type') + ' ' + contents.join(' ') + '\r\n';
-            }
-        });
-    } else {
-        // for backward compability, to be removed soon
-        // assume all contents are in the same bundle group, can be improved upon later
-        var bundle = $(jingle).find('>content').filter(function (idx, content) {
-            //elem.c('bundle', {xmlns:'http://estos.de/ns/bundle'});
-            return $(content).find('>bundle').length > 0;
-        }).map(function (idx, content) {
-                return content.getAttribute('name');
-            }).get();
-        if (bundle.length) {
-            this.raw += 'a=group:BUNDLE ' + bundle.join(' ') + '\r\n';
-        }
-    }
-
-    this.session = this.raw;
-    jingle.find('>content').each(function () {
-        var m = self.jingle2media($(this));
-        self.media.push(m);
-    });
-
-    // reconstruct msid-semantic -- apparently not necessary
-    /*
-     var msid = SDPUtil.parse_ssrc(this.raw);
-     if (msid.hasOwnProperty('mslabel')) {
-     this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
-     }
-     */
-
-    this.raw = this.session + this.media.join('');
-};
-
-// translate a jingle content element into an an SDP media part
-SDP.prototype.jingle2media = function (content) {
-    var media = '',
-        desc = content.find('description'),
-        ssrc = desc.attr('ssrc'),
-        self = this,
-        tmp;
-    var sctp = content.find(
-        '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
-
-    tmp = { media: desc.attr('media') };
-    tmp.port = '1';
-    if (content.attr('senders') == 'rejected') {
-        // estos hack to reject an m-line.
-        tmp.port = '0';
-    }
-    if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
-        if (sctp.length)
-            tmp.proto = 'DTLS/SCTP';
-        else
-            tmp.proto = 'RTP/SAVPF';
-    } else {
-        tmp.proto = 'RTP/AVPF';
-    }
-    if (!sctp.length)
-    {
-        tmp.fmt = desc.find('payload-type').map(
-            function () { return this.getAttribute('id'); }).get();
-        media += SDPUtil.build_mline(tmp) + '\r\n';
-    }
-    else
-    {
-        media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
-        media += 'a=sctpmap:' + sctp.attr('number') +
-            ' ' + sctp.attr('protocol');
-
-        var streamCount = sctp.attr('streams');
-        if (streamCount)
-            media += ' ' + streamCount + '\r\n';
-        else
-            media += '\r\n';
-    }
-
-    media += 'c=IN IP4 0.0.0.0\r\n';
-    if (!sctp.length)
-        media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
-    //tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
-    tmp = content.find('>bundle>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
-    //console.log('transports: '+content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]').length);
-    //console.log('bundle.transports: '+content.find('>bundle>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]').length);
-    //console.log("tmp fingerprint: "+tmp.find('>fingerprint').innerHTML);
-    if (tmp.length) {
-        if (tmp.attr('ufrag')) {
-            media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
-        }
-        if (tmp.attr('pwd')) {
-            media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
-        }
-        tmp.find('>fingerprint').each(function () {
-            // FIXME: check namespace at some point
-            media += 'a=fingerprint:' + this.getAttribute('hash');
-            media += ' ' + $(this).text();
-            media += '\r\n';
-            //console.log("mline "+media);
-            if (this.getAttribute('setup')) {
-                media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
-            }
-        });
-    }
-    switch (content.attr('senders')) {
-        case 'initiator':
-            media += 'a=sendonly\r\n';
-            break;
-        case 'responder':
-            media += 'a=recvonly\r\n';
-            break;
-        case 'none':
-            media += 'a=inactive\r\n';
-            break;
-        case 'both':
-            media += 'a=sendrecv\r\n';
-            break;
-    }
-    media += 'a=mid:' + content.attr('name') + '\r\n';
-    /*if (content.attr('name') == 'video') {
-        media += 'a=x-google-flag:conference' + '\r\n';
-    }*/
-
-    // <description><rtcp-mux/></description>
-    // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
-    // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
-    if (desc.find('rtcp-mux').length) {
-        media += 'a=rtcp-mux\r\n';
-    }
-
-    if (desc.find('encryption').length) {
-        desc.find('encryption>crypto').each(function () {
-            media += 'a=crypto:' + this.getAttribute('tag');
-            media += ' ' + this.getAttribute('crypto-suite');
-            media += ' ' + this.getAttribute('key-params');
-            if (this.getAttribute('session-params')) {
-                media += ' ' + this.getAttribute('session-params');
-            }
-            media += '\r\n';
-        });
-    }
-    desc.find('payload-type').each(function () {
-        media += SDPUtil.build_rtpmap(this) + '\r\n';
-        if ($(this).find('>parameter').length) {
-            media += 'a=fmtp:' + this.getAttribute('id') + ' ';
-            media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
-            media += '\r\n';
-        }
-        // xep-0293
-        media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
-    });
-
-    // xep-0293
-    media += self.RtcpFbFromJingle(desc, '*');
-
-    // xep-0294
-    tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
-    tmp.each(function () {
-        media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
-    });
-
-    content.find('>bundle>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
-        media += SDPUtil.candidateFromJingle(this);
-    });
-
-    // XEP-0339 handle ssrc-group attributes
-    tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
-        var semantics = this.getAttribute('semantics');
-        var ssrcs = $(this).find('>source').map(function() {
-            return this.getAttribute('ssrc');
-        }).get();
-
-        if (ssrcs.length != 0) {
-            media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
-        }
-    });
-
-    tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
-    tmp.each(function () {
-        var ssrc = this.getAttribute('ssrc');
-        $(this).find('>parameter').each(function () {
-            media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
-            if (this.getAttribute('value') && this.getAttribute('value').length)
-                media += ':' + this.getAttribute('value');
-            media += '\r\n';
-        });
-    });
-
-    if (tmp.length === 0) {
-        // fallback to proprietary mapping of a=ssrc lines
-        tmp = content.find('description>ssrc[xmlns="http://estos.de/ns/ssrc"]');
-        if (tmp.length) {
-            media += 'a=ssrc:' + ssrc + ' cname:' + tmp.attr('cname') + '\r\n';
-            media += 'a=ssrc:' + ssrc + ' msid:' + tmp.attr('msid') + '\r\n';
-            media += 'a=ssrc:' + ssrc + ' mslabel:' + tmp.attr('mslabel') + '\r\n';
-            media += 'a=ssrc:' + ssrc + ' label:' + tmp.attr('label') + '\r\n';
-        }
-    }
-    return media;
-};
-
diff --git a/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.util.js b/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.util.js
deleted file mode 100644
index 042a123c32..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe.jingle.sdp.util.js
+++ /dev/null
@@ -1,408 +0,0 @@
-/**
- * Contains utility classes used in SDP class.
- *
- */
-
-/**
- * Class holds a=ssrc lines and media type a=mid
- * @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
- * @param type media type eg. "audio" or "video"(a=mid frm SDP)
- * @constructor
- */
-function ChannelSsrc(ssrc, type) {
-    this.ssrc = ssrc;
-    this.type = type;
-    this.lines = [];
-}
-
-/**
- * Class holds a=ssrc-group: lines
- * @param semantics
- * @param ssrcs
- * @constructor
- */
-function ChannelSsrcGroup(semantics, ssrcs, line) {
-    this.semantics = semantics;
-    this.ssrcs = ssrcs;
-}
-
-/**
- * Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
- * @param channelNumber channel idx in SDP media array.
- * @param mediaType media type(a=mid)
- * @constructor
- */
-function MediaChannel(channelNumber, mediaType) {
-    /**
-     * SDP channel number
-     * @type {*}
-     */
-    this.chNumber = channelNumber;
-    /**
-     * Channel media type(a=mid)
-     * @type {*}
-     */
-    this.mediaType = mediaType;
-    /**
-     * The maps of ssrc numbers to ChannelSsrc objects.
-     */
-    this.ssrcs = {};
-
-    /**
-     * The array of ChannelSsrcGroup objects.
-     * @type {Array}
-     */
-    this.ssrcGroups = [];
-}
-
-SDPUtil = {
-    iceparams: function (mediadesc, sessiondesc) {
-        var data = null;
-        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
-            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
-            data = {
-                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
-                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
-            };
-        }
-        return data;
-    },
-    parse_iceufrag: function (line) {
-        return line.substring(12);
-    },
-    build_iceufrag: function (frag) {
-        return 'a=ice-ufrag:' + frag;
-    },
-    parse_icepwd: function (line) {
-        return line.substring(10);
-    },
-    build_icepwd: function (pwd) {
-        return 'a=ice-pwd:' + pwd;
-    },
-    parse_mid: function (line) {
-        return line.substring(6);
-    },
-    parse_mline: function (line) {
-        var parts = line.substring(2).split(' '),
-            data = {};
-        data.media = parts.shift();
-        data.port = parts.shift();
-        data.proto = parts.shift();
-        if (parts[parts.length - 1] === '') { // trailing whitespace
-            parts.pop();
-        }
-        data.fmt = parts;
-        return data;
-    },
-    build_mline: function (mline) {
-        return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
-    },
-    parse_rtpmap: function (line) {
-        var parts = line.substring(9).split(' '),
-            data = {};
-        data.id = parts.shift();
-        parts = parts[0].split('/');
-        data.name = parts.shift();
-        data.clockrate = parts.shift();
-        data.channels = parts.length ? parts.shift() : '1';
-        return data;
-    },
-    /**
-     * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
-     * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
-     * @returns [SCTP port number, protocol, streams]
-     */
-    parse_sctpmap: function (line)
-    {
-        var parts = line.substring(10).split(' ');
-        var sctpPort = parts[0];
-        var protocol = parts[1];
-        // Stream count is optional
-        var streamCount = parts.length > 2 ? parts[2] : null;
-        return [sctpPort, protocol, streamCount];// SCTP port
-    },
-    build_rtpmap: function (el) {
-        var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
-        if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
-            line += '/' + el.getAttribute('channels');
-        }
-        return line;
-    },
-    parse_crypto: function (line) {
-        var parts = line.substring(9).split(' '),
-            data = {};
-        data.tag = parts.shift();
-        data['crypto-suite'] = parts.shift();
-        data['key-params'] = parts.shift();
-        if (parts.length) {
-            data['session-params'] = parts.join(' ');
-        }
-        return data;
-    },
-    parse_fingerprint: function (line) { // RFC 4572
-        var parts = line.substring(14).split(' '),
-            data = {};
-        data.hash = parts.shift();
-        data.fingerprint = parts.shift();
-        // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
-        return data;
-    },
-    parse_fmtp: function (line) {
-        var parts = line.split(' '),
-            i, key, value,
-            data = [];
-        parts.shift();
-        parts = parts.join(' ').split(';');
-        for (i = 0; i < parts.length; i++) {
-            key = parts[i].split('=')[0];
-            while (key.length && key[0] == ' ') {
-                key = key.substring(1);
-            }
-            value = parts[i].split('=')[1];
-            if (key && value) {
-                data.push({name: key, value: value});
-            } else if (key) {
-                // rfc 4733 (DTMF) style stuff
-                data.push({name: '', value: key});
-            }
-        }
-        return data;
-    },
-    parse_icecandidate: function (line) {
-        var candidate = {},
-            elems = line.split(' ');
-        candidate.foundation = elems[0].substring(12);
-        candidate.component = elems[1];
-        candidate.protocol = elems[2].toLowerCase();
-        candidate.priority = elems[3];
-        candidate.ip = elems[4];
-        candidate.port = elems[5];
-        // elems[6] => "typ"
-        candidate.type = elems[7];
-        candidate.generation = 0; // default value, may be overwritten below
-        for (var i = 8; i < elems.length; i += 2) {
-            switch (elems[i]) {
-                case 'raddr':
-                    candidate['rel-addr'] = elems[i + 1];
-                    break;
-                case 'rport':
-                    candidate['rel-port'] = elems[i + 1];
-                    break;
-                case 'generation':
-                    candidate.generation = elems[i + 1];
-                    break;
-                case 'tcptype':
-                    candidate.tcptype = elems[i + 1];
-                    break;
-                default: // TODO
-                    console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
-            }
-        }
-        candidate.network = '1';
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
-        return candidate;
-    },
-    build_icecandidate: function (cand) {
-        var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
-        line += ' ';
-        switch (cand.type) {
-            case 'srflx':
-            case 'prflx':
-            case 'relay':
-                if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
-                    line += 'raddr';
-                    line += ' ';
-                    line += cand['rel-addr'];
-                    line += ' ';
-                    line += 'rport';
-                    line += ' ';
-                    line += cand['rel-port'];
-                    line += ' ';
-                }
-                break;
-        }
-        if (cand.hasOwnAttribute('tcptype')) {
-            line += 'tcptype';
-            line += ' ';
-            line += cand.tcptype;
-            line += ' ';
-        }
-        line += 'generation';
-        line += ' ';
-        line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
-        return line;
-    },
-    parse_ssrc: function (desc) {
-        // proprietary mapping of a=ssrc lines
-        // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
-        // and parse according to that
-        var lines = desc.split('\r\n'),
-            data = {};
-        for (var i = 0; i < lines.length; i++) {
-            if (lines[i].substring(0, 7) == 'a=ssrc:') {
-                var idx = lines[i].indexOf(' ');
-                data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
-            }
-        }
-        return data;
-    },
-    parse_rtcpfb: function (line) {
-        var parts = line.substr(10).split(' ');
-        var data = {};
-        data.pt = parts.shift();
-        data.type = parts.shift();
-        data.params = parts;
-        return data;
-    },
-    parse_extmap: function (line) {
-        var parts = line.substr(9).split(' ');
-        var data = {};
-        data.value = parts.shift();
-        if (data.value.indexOf('/') != -1) {
-            data.direction = data.value.substr(data.value.indexOf('/') + 1);
-            data.value = data.value.substr(0, data.value.indexOf('/'));
-        } else {
-            data.direction = 'both';
-        }
-        data.uri = parts.shift();
-        data.params = parts;
-        return data;
-    },
-    find_line: function (haystack, needle, sessionpart) {
-        var lines = haystack.split('\r\n');
-        for (var i = 0; i < lines.length; i++) {
-            if (lines[i].substring(0, needle.length) == needle) {
-                return lines[i];
-            }
-        }
-        if (!sessionpart) {
-            return false;
-        }
-        // search session part
-        lines = sessionpart.split('\r\n');
-        for (var j = 0; j < lines.length; j++) {
-            if (lines[j].substring(0, needle.length) == needle) {
-                return lines[j];
-            }
-        }
-        return false;
-    },
-    find_lines: function (haystack, needle, sessionpart) {
-        var lines = haystack.split('\r\n'),
-            needles = [];
-        for (var i = 0; i < lines.length; i++) {
-            if (lines[i].substring(0, needle.length) == needle)
-                needles.push(lines[i]);
-        }
-        if (needles.length || !sessionpart) {
-            return needles;
-        }
-        // search session part
-        lines = sessionpart.split('\r\n');
-        for (var j = 0; j < lines.length; j++) {
-            if (lines[j].substring(0, needle.length) == needle) {
-                needles.push(lines[j]);
-            }
-        }
-        return needles;
-    },
-    candidateToJingle: function (line) {
-        // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
-        //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
-        if (line.indexOf('candidate:') === 0) {
-            line = 'a=' + line;
-        } else if (line.substring(0, 12) != 'a=candidate:') {
-            console.log('parseCandidate called with a line that is not a candidate line');
-            console.log(line);
-            return null;
-        }
-        if (line.substring(line.length - 2) == '\r\n') // chomp it
-            line = line.substring(0, line.length - 2);
-        var candidate = {},
-            elems = line.split(' '),
-            i;
-        if (elems[6] != 'typ') {
-            console.log('did not find typ in the right place');
-            console.log(line);
-            return null;
-        }
-        candidate.foundation = elems[0].substring(12);
-        candidate.component = elems[1];
-        candidate.protocol = elems[2].toLowerCase();
-        candidate.priority = elems[3];
-        candidate.ip = elems[4];
-        candidate.port = elems[5];
-        // elems[6] => "typ"
-        candidate.type = elems[7];
-
-        candidate.generation = '0'; // default, may be overwritten below
-        for (i = 8; i < elems.length; i += 2) {
-            switch (elems[i]) {
-                case 'raddr':
-                    candidate['rel-addr'] = elems[i + 1];
-                    break;
-                case 'rport':
-                    candidate['rel-port'] = elems[i + 1];
-                    break;
-                case 'generation':
-                    candidate.generation = elems[i + 1];
-                    break;
-                case 'tcptype':
-                    candidate.tcptype = elems[i + 1];
-                    break;
-                default: // TODO
-                    console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
-            }
-        }
-        candidate.network = '1';
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
-        return candidate;
-    },
-    candidateFromJingle: function (cand) {
-        var line = 'a=candidate:';
-        line += cand.getAttribute('foundation');
-        line += ' ';
-        line += cand.getAttribute('component');
-        line += ' ';
-        line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
-        line += ' ';
-        line += cand.getAttribute('priority');
-        line += ' ';
-        line += cand.getAttribute('ip');
-        line += ' ';
-        line += cand.getAttribute('port');
-        line += ' ';
-        line += 'typ';
-        line += ' ' + cand.getAttribute('type');
-        line += ' ';
-        switch (cand.getAttribute('type')) {
-            case 'srflx':
-            case 'prflx':
-            case 'relay':
-                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
-                    line += 'raddr';
-                    line += ' ';
-                    line += cand.getAttribute('rel-addr');
-                    line += ' ';
-                    line += 'rport';
-                    line += ' ';
-                    line += cand.getAttribute('rel-port');
-                    line += ' ';
-                }
-                break;
-        }
-        if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
-            line += 'tcptype';
-            line += ' ';
-            line += cand.getAttribute('tcptype');
-            line += ' ';
-        }
-        line += 'generation';
-        line += ' ';
-        line += cand.getAttribute('generation') || '0';
-        return line + '\r\n';
-    }
-};
-
-exports.SDPUtil = SDPUtil;
-
diff --git a/contrib/jitsimeetbridge/unjingle/strophe/XMLHttpRequest.js b/contrib/jitsimeetbridge/unjingle/strophe/XMLHttpRequest.js
deleted file mode 100644
index 9c45c2df18..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe/XMLHttpRequest.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/**
- * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
- *
- * This can be used with JS designed for browsers to improve reuse of code and
- * allow the use of existing libraries.
- *
- * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
- *
- * @todo SSL Support
- * @author Dan DeFelippi <dan@driverdan.com>
- * @license MIT
- */
-
-var Url = require("url")
-	,sys = require("util");
-
-exports.XMLHttpRequest = function() {
-	/**
-	 * Private variables
-	 */
-	var self = this;
-	var http = require('http');
-	var https = require('https');
-
-	// Holds http.js objects
-	var client;
-	var request;
-	var response;
-	
-	// Request settings
-	var settings = {};
-	
-	// Set some default headers
-	var defaultHeaders = {
-		"User-Agent": "node.js",
-		"Accept": "*/*",
-	};
-	
-	var headers = defaultHeaders;
-	
-	/**
-	 * Constants
-	 */
-	this.UNSENT = 0;
-	this.OPENED = 1;
-	this.HEADERS_RECEIVED = 2;
-	this.LOADING = 3;
-	this.DONE = 4;
-
-	/**
-	 * Public vars
-	 */
-	// Current state
-	this.readyState = this.UNSENT;
-
-	// default ready state change handler in case one is not set or is set late
-	this.onreadystatechange = function() {};
-
-	// Result & response
-	this.responseText = "";
-	this.responseXML = "";
-	this.status = null;
-	this.statusText = null;
-		
-	/**
-	 * Open the connection. Currently supports local server requests.
-	 *
-	 * @param string method Connection method (eg GET, POST)
-	 * @param string url URL for the connection.
-	 * @param boolean async Asynchronous connection. Default is true.
-	 * @param string user Username for basic authentication (optional)
-	 * @param string password Password for basic authentication (optional)
-	 */
-	this.open = function(method, url, async, user, password) {
-		settings = {
-			"method": method,
-			"url": url,
-			"async": async || null,
-			"user": user || null,
-			"password": password || null
-		};
-		
-		this.abort();
-
-		setState(this.OPENED);
-	};
-	
-	/**
-	 * Sets a header for the request.
-	 *
-	 * @param string header Header name
-	 * @param string value Header value
-	 */
-	this.setRequestHeader = function(header, value) {
-		headers[header] = value;
-	};
-	
-	/**
-	 * Gets a header from the server response.
-	 *
-	 * @param string header Name of header to get.
-	 * @return string Text of the header or null if it doesn't exist.
-	 */
-	this.getResponseHeader = function(header) {
-		if (this.readyState > this.OPENED && response.headers[header]) {
-			return header + ": " + response.headers[header];
-		}
-		
-		return null;
-	};
-	
-	/**
-	 * Gets all the response headers.
-	 *
-	 * @return string 
-	 */
-	this.getAllResponseHeaders = function() {
-		if (this.readyState < this.HEADERS_RECEIVED) {
-			throw "INVALID_STATE_ERR: Headers have not been received.";
-		}
-		var result = "";
-		
-		for (var i in response.headers) {
-			result += i + ": " + response.headers[i] + "\r\n";
-		}
-		return result.substr(0, result.length - 2);
-	};
-
-	/**
-	 * Sends the request to the server.
-	 *
-	 * @param string data Optional data to send as request body.
-	 */
-	this.send = function(data) {
-		if (this.readyState != this.OPENED) {
-			throw "INVALID_STATE_ERR: connection must be opened before send() is called";
-		}
-		
-		var ssl = false;
-		var url = Url.parse(settings.url);
-		
-		// Determine the server
-		switch (url.protocol) {
-			case 'https:':
-				ssl = true;
-				// SSL & non-SSL both need host, no break here.
-			case 'http:':
-				var host = url.hostname;
-				break;
-			
-			case undefined:
-			case '':
-				var host = "localhost";
-				break;
-			
-			default:
-				throw "Protocol not supported.";
-		}
-
-		// Default to port 80. If accessing localhost on another port be sure
-		// to use http://localhost:port/path
-		var port = url.port || (ssl ? 443 : 80);
-		// Add query string if one is used
-		var uri = url.pathname + (url.search ? url.search : '');
-		
-		// Set the Host header or the server may reject the request
-		this.setRequestHeader("Host", host);
-		
-		// Set content length header
-		if (settings.method == "GET" || settings.method == "HEAD") {
-			data = null;
-		} else if (data) {
-			this.setRequestHeader("Content-Length", Buffer.byteLength(data));
-			
-			if (!headers["Content-Type"]) {
-				this.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
-			}
-		}
-
-		// Use the proper protocol
-		var doRequest = ssl ? https.request : http.request;
-
-		var options = {
-		    host: host,
-		    port: port,
-		    path: uri,
-		    method: settings.method,
-		    headers: headers, 
-                    agent: false
-		};
-		
-		var req = doRequest(options, function(res) {
-			response = res;
-			response.setEncoding("utf8");
-
-			setState(self.HEADERS_RECEIVED);
-			self.status = response.statusCode;
-
-			response.on('data', function(chunk) {
-				// Make sure there's some data
-				if (chunk) {
-					self.responseText += chunk;
-				}
-				setState(self.LOADING);
-			});
-
-			response.on('end', function() {
-				setState(self.DONE);
-			});
-
-			response.on('error', function() {
-				self.handleError(error);
-			});
-		}).on('error', function(error) {
-			self.handleError(error);
-		});
-
-		req.setHeader("Connection", "Close");
-
-		// Node 0.4 and later won't accept empty data. Make sure it's needed.
-		if (data) {
-			req.write(data);
-		}
-
-		req.end();
-	};
-
-	this.handleError = function(error) {
-		this.status = 503;
-		this.statusText = error;
-		this.responseText = error.stack;
-		setState(this.DONE);
-	};
-
-	/**
-	 * Aborts a request.
-	 */
-	this.abort = function() {
-		headers = defaultHeaders;
-		this.readyState = this.UNSENT;
-		this.responseText = "";
-		this.responseXML = "";
-	};
-	
-	/**
-	 * Changes readyState and calls onreadystatechange.
-	 *
-	 * @param int state New state
-	 */
-	var setState = function(state) {
-		self.readyState = state;
-		self.onreadystatechange();
-	}
-};
diff --git a/contrib/jitsimeetbridge/unjingle/strophe/base64.js b/contrib/jitsimeetbridge/unjingle/strophe/base64.js
deleted file mode 100644
index 418caac050..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe/base64.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// 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;
-})();
-
-// Nodify
-exports.Base64 = Base64;
diff --git a/contrib/jitsimeetbridge/unjingle/strophe/md5.js b/contrib/jitsimeetbridge/unjingle/strophe/md5.js
deleted file mode 100644
index 5334325e2f..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe/md5.js
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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;
-})();
-
-// Nodify
-exports.MD5 = MD5;
diff --git a/contrib/jitsimeetbridge/unjingle/strophe/strophe.js b/contrib/jitsimeetbridge/unjingle/strophe/strophe.js
deleted file mode 100644
index 06d426cdec..0000000000
--- a/contrib/jitsimeetbridge/unjingle/strophe/strophe.js
+++ /dev/null
@@ -1,3256 +0,0 @@
-/*
-    This program is distributed under the terms of the MIT license.
-    Please see the LICENSE file for details.
-
-    Copyright 2006-2008, OGG, LLC
-*/
-
-/* jslint configuration: */
-/*global document, window, setTimeout, clearTimeout, console,
-    XMLHttpRequest, ActiveXObject,
-    Base64, MD5,
-    Strophe, $build, $msg, $iq, $pres */
-
-/** File: strophe.js
- *  A JavaScript library for XMPP BOSH.
- *
- *  This is the JavaScript version of the Strophe library.  Since JavaScript
- *  has no facilities for persistent TCP connections, this library uses
- *  Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate
- *  a persistent, stateful, two-way connection to an XMPP server.  More
- *  information on BOSH can be found in XEP 124.
- */
-
-/** PrivateFunction: Function.prototype.bind
- *  Bind a function to an instance.
- *
- *  This Function object extension method creates a bound method similar
- *  to those in Python.  This means that the 'this' object will point
- *  to the instance you want.  See
- *  <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and 
- *  <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a>
- *  for a complete explanation.
- *
- *  This extension already exists in some browsers (namely, Firefox 3), but
- *  we provide it to support those that don't.
- *
- *  Parameters:
- *    (Object) obj - The object that will become 'this' in the bound function.
- *    (Object) argN - An option argument that will be prepended to the 
- *      arguments given for the function call
- *
- *  Returns:
- *    The bound function.
- */
-
-/* Make it work on node.js: Nodify
- *
- * Steps:
- * 1. Create the global objects: window, document, Base64, MD5 and XMLHttpRequest
- * 2. Use the node-XMLHttpRequest module.
- * 3. Use jsdom for the document object - since it supports DOM functions.
- * 4. Replace all calls to childNodes with _childNodes (since the former doesn't
- *    seem to work on jsdom).
- * 5. While getting the response from XMLHttpRequest, manually convert the text
- *    data to XML.
- * 6. All calls to nodeName should replaced by nodeName.toLowerCase() since jsdom
- *    seems to always convert node names to upper case.
- *
- */
-var XMLHttpRequest = require('./XMLHttpRequest.js').XMLHttpRequest;
-var Base64         = require('./base64.js').Base64;
-var MD5            = require('./md5.js').MD5;
-var jsdom          = require("jsdom").jsdom;
-
-document = jsdom("<html><head></head><body></body></html>"),
-
-window = {
-    XMLHttpRequest: XMLHttpRequest, 
-    Base64: Base64, 
-    MD5: MD5
-};
-
-exports.Strophe = window;
-
-if (!Function.prototype.bind) {
-    Function.prototype.bind = function (obj /*, arg1, arg2, ... */)
-    {
-        var func = this;
-        var _slice = Array.prototype.slice;
-        var _concat = Array.prototype.concat;
-        var _args = _slice.call(arguments, 1);
-        
-        return function () {
-            return func.apply(obj ? obj : this,
-                              _concat.call(_args,
-                                           _slice.call(arguments, 0)));
-        };
-    };
-}
-
-/** PrivateFunction: Array.prototype.indexOf
- *  Return the index of an object in an array.
- *
- *  This function is not supplied by some JavaScript implementations, so
- *  we provide it if it is missing.  This code is from:
- *  http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
- *
- *  Parameters:
- *    (Object) elt - The object to look for.
- *    (Integer) from - The index from which to start looking. (optional).
- *
- *  Returns:
- *    The index of elt in the array or -1 if not found.
- */
-if (!Array.prototype.indexOf)
-{
-    Array.prototype.indexOf = function(elt /*, from*/)
-    {
-        var len = this.length;
-
-        var from = Number(arguments[1]) || 0;
-        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
-        if (from < 0) {
-            from += len;
-        }
-
-        for (; from < len; from++) {
-            if (from in this && this[from] === elt) {
-                return from;
-            }
-        }
-
-        return -1;
-    };
-}
-
-/* All of the Strophe globals are defined in this special function below so
- * that references to the globals become closures.  This will ensure that
- * on page reload, these references will still be available to callbacks
- * that are still executing.
- */
-
-(function (callback) {
-var Strophe;
-
-/** Function: $build
- *  Create a Strophe.Builder.
- *  This is an alias for 'new Strophe.Builder(name, attrs)'.
- *
- *  Parameters:
- *    (String) name - The root element name.
- *    (Object) attrs - The attributes for the root element in object notation.
- *
- *  Returns:
- *    A new Strophe.Builder object.
- */
-function $build(name, attrs) { return new Strophe.Builder(name, attrs); }
-/** Function: $msg
- *  Create a Strophe.Builder with a <message/> element as the root.
- *
- *  Parmaeters:
- *    (Object) attrs - The <message/> element attributes in object notation.
- *
- *  Returns:
- *    A new Strophe.Builder object.
- */
-function $msg(attrs) { return new Strophe.Builder("message", attrs); }
-/** Function: $iq
- *  Create a Strophe.Builder with an <iq/> element as the root.
- *
- *  Parameters:
- *    (Object) attrs - The <iq/> element attributes in object notation.
- *
- *  Returns:
- *    A new Strophe.Builder object.
- */
-function $iq(attrs) { return new Strophe.Builder("iq", attrs); }
-/** Function: $pres
- *  Create a Strophe.Builder with a <presence/> element as the root.
- *
- *  Parameters:
- *    (Object) attrs - The <presence/> element attributes in object notation.
- *
- *  Returns:
- *    A new Strophe.Builder object.
- */
-function $pres(attrs) { return new Strophe.Builder("presence", attrs); }
-
-/** Class: Strophe
- *  An object container for all Strophe library functions.
- *
- *  This class is just a container for all the objects and constants
- *  used in the library.  It is not meant to be instantiated, but to
- *  provide a namespace for library objects, constants, and functions.
- */
-Strophe = {
-    /** Constant: VERSION
-     *  The version of the Strophe library. Unreleased builds will have
-     *  a version of head-HASH where HASH is a partial revision.
-     */
-    VERSION: "@VERSION@",
-
-    /** Constants: XMPP Namespace Constants
-     *  Common namespace constants from the XMPP RFCs and XEPs.
-     *
-     *  NS.HTTPBIND - HTTP BIND namespace from XEP 124.
-     *  NS.BOSH - BOSH namespace from XEP 206.
-     *  NS.CLIENT - Main XMPP client namespace.
-     *  NS.AUTH - Legacy authentication namespace.
-     *  NS.ROSTER - Roster operations namespace.
-     *  NS.PROFILE - Profile namespace.
-     *  NS.DISCO_INFO - Service discovery info namespace from XEP 30.
-     *  NS.DISCO_ITEMS - Service discovery items namespace from XEP 30.
-     *  NS.MUC - Multi-User Chat namespace from XEP 45.
-     *  NS.SASL - XMPP SASL namespace from RFC 3920.
-     *  NS.STREAM - XMPP Streams namespace from RFC 3920.
-     *  NS.BIND - XMPP Binding namespace from RFC 3920.
-     *  NS.SESSION - XMPP Session namespace from RFC 3920.
-     */
-    NS: {
-        HTTPBIND: "http://jabber.org/protocol/httpbind",
-        BOSH: "urn:xmpp:xbosh",
-        CLIENT: "jabber:client",
-        AUTH: "jabber:iq:auth",
-        ROSTER: "jabber:iq:roster",
-        PROFILE: "jabber:iq:profile",
-        DISCO_INFO: "http://jabber.org/protocol/disco#info",
-        DISCO_ITEMS: "http://jabber.org/protocol/disco#items",
-        MUC: "http://jabber.org/protocol/muc",
-        SASL: "urn:ietf:params:xml:ns:xmpp-sasl",
-        STREAM: "http://etherx.jabber.org/streams",
-        BIND: "urn:ietf:params:xml:ns:xmpp-bind",
-        SESSION: "urn:ietf:params:xml:ns:xmpp-session",
-        VERSION: "jabber:iq:version",
-        STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas"
-    },
-
-    /** Function: addNamespace
-     *  This function is used to extend the current namespaces in
-     *	Strophe.NS.  It takes a key and a value with the key being the
-     *	name of the new namespace, with its actual value.
-     *	For example:
-     *	Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub");
-     *
-     *  Parameters:
-     *    (String) name - The name under which the namespace will be
-     *      referenced under Strophe.NS
-     *    (String) value - The actual namespace.
-     */
-    addNamespace: function (name, value)
-    {
-	Strophe.NS[name] = value;
-    },
-
-    /** Constants: Connection Status Constants
-     *  Connection status constants for use by the connection handler
-     *  callback.
-     *
-     *  Status.ERROR - An error has occurred
-     *  Status.CONNECTING - The connection is currently being made
-     *  Status.CONNFAIL - The connection attempt failed
-     *  Status.AUTHENTICATING - The connection is authenticating
-     *  Status.AUTHFAIL - The authentication attempt failed
-     *  Status.CONNECTED - The connection has succeeded
-     *  Status.DISCONNECTED - The connection has been terminated
-     *  Status.DISCONNECTING - The connection is currently being terminated
-     *  Status.ATTACHED - The connection has been attached
-     */
-    Status: {
-        ERROR: 0,
-        CONNECTING: 1,
-        CONNFAIL: 2,
-        AUTHENTICATING: 3,
-        AUTHFAIL: 4,
-        CONNECTED: 5,
-        DISCONNECTED: 6,
-        DISCONNECTING: 7,
-        ATTACHED: 8
-    },
-
-    /** Constants: Log Level Constants
-     *  Logging level indicators.
-     *
-     *  LogLevel.DEBUG - Debug output
-     *  LogLevel.INFO - Informational output
-     *  LogLevel.WARN - Warnings
-     *  LogLevel.ERROR - Errors
-     *  LogLevel.FATAL - Fatal errors
-     */
-    LogLevel: {
-        DEBUG: 0,
-        INFO: 1,
-        WARN: 2,
-        ERROR: 3,
-        FATAL: 4
-    },
-
-    /** PrivateConstants: DOM Element Type Constants
-     *  DOM element types.
-     *
-     *  ElementType.NORMAL - Normal element.
-     *  ElementType.TEXT - Text data element.
-     */
-    ElementType: {
-        NORMAL: 1,
-        TEXT: 3
-    },
-
-    /** PrivateConstants: Timeout Values
-     *  Timeout values for error states.  These values are in seconds.
-     *  These should not be changed unless you know exactly what you are
-     *  doing.
-     *
-     *  TIMEOUT - Timeout multiplier. A waiting request will be considered
-     *      failed after Math.floor(TIMEOUT * wait) seconds have elapsed.
-     *      This defaults to 1.1, and with default wait, 66 seconds.
-     *  SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where
-     *      Strophe can detect early failure, it will consider the request
-     *      failed if it doesn't return after
-     *      Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed.
-     *      This defaults to 0.1, and with default wait, 6 seconds.
-     */
-    TIMEOUT: 1.1,
-    SECONDARY_TIMEOUT: 0.1,
-
-    /** Function: forEachChild
-     *  Map a function over some or all child elements of a given element.
-     *
-     *  This is a small convenience function for mapping a function over
-     *  some or all of the children of an element.  If elemName is null, all
-     *  children will be passed to the function, otherwise only children
-     *  whose tag names match elemName will be passed.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The element to operate on.
-     *    (String) elemName - The child element tag name filter.
-     *    (Function) func - The function to apply to each child.  This
-     *      function should take a single argument, a DOM element.
-     */
-    forEachChild: function (elem, elemName, func)
-    {
-        var i, childNode;
-
-        for (i = 0; i < elem._childNodes.length; i++) {
-            childNode = elem._childNodes[i];
-            if (childNode.nodeType == Strophe.ElementType.NORMAL &&
-                (!elemName || this.isTagEqual(childNode, elemName))) {
-                func(childNode);
-            }
-        }
-    },
-
-    /** Function: isTagEqual
-     *  Compare an element's tag name with a string.
-     *
-     *  This function is case insensitive.
-     *
-     *  Parameters:
-     *    (XMLElement) el - A DOM element.
-     *    (String) name - The element name.
-     *
-     *  Returns:
-     *    true if the element's tag name matches _el_, and false
-     *    otherwise.
-     */
-    isTagEqual: function (el, name)
-    {
-        return el.tagName.toLowerCase() == name.toLowerCase();
-    },
-
-    /** PrivateVariable: _xmlGenerator
-     *  _Private_ variable that caches a DOM document to
-     *  generate elements.
-     */
-    _xmlGenerator: null,
-
-    /** PrivateFunction: _makeGenerator
-     *  _Private_ function that creates a dummy XML DOM document to serve as
-     *  an element and text node generator.
-     */
-    _makeGenerator: function () {
-        var doc;
-
-        if (window.ActiveXObject) {
-            doc = this._getIEXmlDom();
-            doc.appendChild(doc.createElement('strophe'));
-        } else {
-            doc = document.implementation
-                .createDocument('jabber:client', 'strophe', null);
-        }
-
-        return doc;
-    },
-
-    /** Function: xmlGenerator
-     *  Get the DOM document to generate elements.
-     *
-     *  Returns:
-     *    The currently used DOM document.
-     */
-    xmlGenerator: function () {
-        if (!Strophe._xmlGenerator) {
-            Strophe._xmlGenerator = Strophe._makeGenerator();
-        }
-        return Strophe._xmlGenerator;
-    },
-
-    /** PrivateFunction: _getIEXmlDom
-     *  Gets IE xml doc object
-     *
-     *  Returns:
-     *    A Microsoft XML DOM Object
-     *  See Also:
-     *    http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx
-     */
-    _getIEXmlDom : function() {
-        var doc = null;
-        var docStrings = [
-            "Msxml2.DOMDocument.6.0",
-            "Msxml2.DOMDocument.5.0",
-            "Msxml2.DOMDocument.4.0",
-            "MSXML2.DOMDocument.3.0",
-            "MSXML2.DOMDocument",
-            "MSXML.DOMDocument",
-            "Microsoft.XMLDOM"
-        ];
-
-        for (var d = 0; d < docStrings.length; d++) {
-            if (doc === null) {
-                try {
-                    doc = new ActiveXObject(docStrings[d]);
-                } catch (e) {
-                    doc = null;
-                }
-            } else {
-                break;
-            }
-        }
-
-        return doc;
-    },
-
-    /** Function: xmlElement
-     *  Create an XML DOM element.
-     *
-     *  This function creates an XML DOM element correctly across all
-     *  implementations. Note that these are not HTML DOM elements, which
-     *  aren't appropriate for XMPP stanzas.
-     *
-     *  Parameters:
-     *    (String) name - The name for the element.
-     *    (Array|Object) attrs - An optional array or object containing
-     *      key/value pairs to use as element attributes. The object should
-     *      be in the format {'key': 'value'} or {key: 'value'}. The array
-     *      should have the format [['key1', 'value1'], ['key2', 'value2']].
-     *    (String) text - The text child data for the element.
-     *
-     *  Returns:
-     *    A new XML DOM element.
-     */
-    xmlElement: function (name)
-    {
-        if (!name) { return null; }
-
-        var node = Strophe.xmlGenerator().createElement(name);
-
-        // FIXME: this should throw errors if args are the wrong type or
-        // there are more than two optional args
-        var a, i, k;
-        for (a = 1; a < arguments.length; a++) {
-            if (!arguments[a]) { continue; }
-            if (typeof(arguments[a]) == "string" ||
-                typeof(arguments[a]) == "number") {
-                node.appendChild(Strophe.xmlTextNode(arguments[a]));
-            } else if (typeof(arguments[a]) == "object" &&
-                       typeof(arguments[a].sort) == "function") {
-                for (i = 0; i < arguments[a].length; i++) {
-                    if (typeof(arguments[a][i]) == "object" &&
-                        typeof(arguments[a][i].sort) == "function") {
-                        node.setAttribute(arguments[a][i][0],
-                                          arguments[a][i][1]);
-                    }
-                }
-            } else if (typeof(arguments[a]) == "object") {
-                for (k in arguments[a]) {
-                    if (arguments[a].hasOwnProperty(k)) {
-                        node.setAttribute(k, arguments[a][k]);
-                    }
-                }
-            }
-        }
-
-        return node;
-    },
-
-    /*  Function: xmlescape
-     *  Excapes invalid xml characters.
-     *
-     *  Parameters:
-     *     (String) text - text to escape.
-     *
-     *	Returns:
-     *      Escaped text.
-     */
-    xmlescape: function(text)
-    {
-	text = text.replace(/\&/g, "&amp;");
-        text = text.replace(/</g,  "&lt;");
-        text = text.replace(/>/g,  "&gt;");
-        return text;
-    },
-
-    /** Function: xmlTextNode
-     *  Creates an XML DOM text node.
-     *
-     *  Provides a cross implementation version of document.createTextNode.
-     *
-     *  Parameters:
-     *    (String) text - The content of the text node.
-     *
-     *  Returns:
-     *    A new XML DOM text node.
-     */
-    xmlTextNode: function (text)
-    {
-	//ensure text is escaped
-	text = Strophe.xmlescape(text);
-
-        return Strophe.xmlGenerator().createTextNode(text);
-    },
-
-    /** Function: getText
-     *  Get the concatenation of all text children of an element.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - A DOM element.
-     *
-     *  Returns:
-     *    A String with the concatenated text of all text element children.
-     */
-    getText: function (elem)
-    {
-        if (!elem) { return null; }
-
-        var str = "";
-        if (elem._childNodes.length === 0 && elem.nodeType ==
-            Strophe.ElementType.TEXT) {
-            str += elem.nodeValue;
-        }
-
-        for (var i = 0; i < elem._childNodes.length; i++) {
-            if (elem._childNodes[i].nodeType == Strophe.ElementType.TEXT) {
-                str += elem._childNodes[i].nodeValue;
-            }
-        }
-
-        return str;
-    },
-
-    /** Function: copyElement
-     *  Copy an XML DOM element.
-     *
-     *  This function copies a DOM element and all its descendants and returns
-     *  the new copy.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - A DOM element.
-     *
-     *  Returns:
-     *    A new, copied DOM element tree.
-     */
-    copyElement: function (elem)
-    {
-        var i, el;
-        if (elem.nodeType == Strophe.ElementType.NORMAL) {
-            el = Strophe.xmlElement(elem.tagName);
-
-            for (i = 0; i < elem.attributes.length; i++) {
-                el.setAttribute(elem.attributes[i].nodeName.toLowerCase(),
-                                elem.attributes[i].value);
-            }
-
-            for (i = 0; i < elem._childNodes.length; i++) {
-                el.appendChild(Strophe.copyElement(elem._childNodes[i]));
-            }
-        } else if (elem.nodeType == Strophe.ElementType.TEXT) {
-            el = Strophe.xmlTextNode(elem.nodeValue);
-        }
-
-        return el;
-    },
-
-    /** Function: escapeNode
-     *  Escape the node part (also called local part) of a JID.
-     *
-     *  Parameters:
-     *    (String) node - A node (or local part).
-     *
-     *  Returns:
-     *    An escaped node (or local part).
-     */
-    escapeNode: function (node)
-    {
-        return node.replace(/^\s+|\s+$/g, '')
-            .replace(/\\/g,  "\\5c")
-            .replace(/ /g,   "\\20")
-            .replace(/\"/g,  "\\22")
-            .replace(/\&/g,  "\\26")
-            .replace(/\'/g,  "\\27")
-            .replace(/\//g,  "\\2f")
-            .replace(/:/g,   "\\3a")
-            .replace(/</g,   "\\3c")
-            .replace(/>/g,   "\\3e")
-            .replace(/@/g,   "\\40");
-    },
-
-    /** Function: unescapeNode
-     *  Unescape a node part (also called local part) of a JID.
-     *
-     *  Parameters:
-     *    (String) node - A node (or local part).
-     *
-     *  Returns:
-     *    An unescaped node (or local part).
-     */
-    unescapeNode: function (node)
-    {
-        return node.replace(/\\20/g, " ")
-            .replace(/\\22/g, '"')
-            .replace(/\\26/g, "&")
-            .replace(/\\27/g, "'")
-            .replace(/\\2f/g, "/")
-            .replace(/\\3a/g, ":")
-            .replace(/\\3c/g, "<")
-            .replace(/\\3e/g, ">")
-            .replace(/\\40/g, "@")
-            .replace(/\\5c/g, "\\");
-    },
-
-    /** Function: getNodeFromJid
-     *  Get the node portion of a JID String.
-     *
-     *  Parameters:
-     *    (String) jid - A JID.
-     *
-     *  Returns:
-     *    A String containing the node.
-     */
-    getNodeFromJid: function (jid)
-    {
-        if (jid.indexOf("@") < 0) { return null; }
-        return jid.split("@")[0];
-    },
-
-    /** Function: getDomainFromJid
-     *  Get the domain portion of a JID String.
-     *
-     *  Parameters:
-     *    (String) jid - A JID.
-     *
-     *  Returns:
-     *    A String containing the domain.
-     */
-    getDomainFromJid: function (jid)
-    {
-        var bare = Strophe.getBareJidFromJid(jid);
-        if (bare.indexOf("@") < 0) {
-            return bare;
-        } else {
-            var parts = bare.split("@");
-            parts.splice(0, 1);
-            return parts.join('@');
-        }
-    },
-
-    /** Function: getResourceFromJid
-     *  Get the resource portion of a JID String.
-     *
-     *  Parameters:
-     *    (String) jid - A JID.
-     *
-     *  Returns:
-     *    A String containing the resource.
-     */
-    getResourceFromJid: function (jid)
-    {
-        var s = jid.split("/");
-        if (s.length < 2) { return null; }
-        s.splice(0, 1);
-        return s.join('/');
-    },
-
-    /** Function: getBareJidFromJid
-     *  Get the bare JID from a JID String.
-     *
-     *  Parameters:
-     *    (String) jid - A JID.
-     *
-     *  Returns:
-     *    A String containing the bare JID.
-     */
-    getBareJidFromJid: function (jid)
-    {
-        return jid ? jid.split("/")[0] : null;
-    },
-
-    /** Function: log
-     *  User overrideable logging function.
-     *
-     *  This function is called whenever the Strophe library calls any
-     *  of the logging functions.  The default implementation of this
-     *  function does nothing.  If client code wishes to handle the logging
-     *  messages, it should override this with
-     *  > Strophe.log = function (level, msg) {
-     *  >   (user code here)
-     *  > };
-     *
-     *  Please note that data sent and received over the wire is logged
-     *  via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
-     *
-     *  The different levels and their meanings are
-     *
-     *    DEBUG - Messages useful for debugging purposes.
-     *    INFO - Informational messages.  This is mostly information like
-     *      'disconnect was called' or 'SASL auth succeeded'.
-     *    WARN - Warnings about potential problems.  This is mostly used
-     *      to report transient connection errors like request timeouts.
-     *    ERROR - Some error occurred.
-     *    FATAL - A non-recoverable fatal error occurred.
-     *
-     *  Parameters:
-     *    (Integer) level - The log level of the log message.  This will
-     *      be one of the values in Strophe.LogLevel.
-     *    (String) msg - The log message.
-     */
-    log: function (level, msg)
-    {
-        return;
-    },
-
-    /** Function: debug
-     *  Log a message at the Strophe.LogLevel.DEBUG level.
-     *
-     *  Parameters:
-     *    (String) msg - The log message.
-     */
-    debug: function(msg)
-    {
-        this.log(this.LogLevel.DEBUG, msg);
-    },
-
-    /** Function: info
-     *  Log a message at the Strophe.LogLevel.INFO level.
-     *
-     *  Parameters:
-     *    (String) msg - The log message.
-     */
-    info: function (msg)
-    {
-        this.log(this.LogLevel.INFO, msg);
-    },
-
-    /** Function: warn
-     *  Log a message at the Strophe.LogLevel.WARN level.
-     *
-     *  Parameters:
-     *    (String) msg - The log message.
-     */
-    warn: function (msg)
-    {
-        this.log(this.LogLevel.WARN, msg);
-    },
-
-    /** Function: error
-     *  Log a message at the Strophe.LogLevel.ERROR level.
-     *
-     *  Parameters:
-     *    (String) msg - The log message.
-     */
-    error: function (msg)
-    {
-        this.log(this.LogLevel.ERROR, msg);
-    },
-
-    /** Function: fatal
-     *  Log a message at the Strophe.LogLevel.FATAL level.
-     *
-     *  Parameters:
-     *    (String) msg - The log message.
-     */
-    fatal: function (msg)
-    {
-        this.log(this.LogLevel.FATAL, msg);
-    },
-
-    /** Function: serialize
-     *  Render a DOM element and all descendants to a String.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - A DOM element.
-     *
-     *  Returns:
-     *    The serialized element tree as a String.
-     */
-    serialize: function (elem)
-    {
-        var result;
-
-        if (!elem) { return null; }
-
-        if (typeof(elem.tree) === "function") {
-            elem = elem.tree();
-        }
-
-        var nodeName = elem.nodeName.toLowerCase();
-        var i, child;
-
-        if (elem.getAttribute("_realname")) {
-            nodeName = elem.getAttribute("_realname").toLowerCase();
-        }
-
-        result = "<" + nodeName.toLowerCase();
-        for (i = 0; i < elem.attributes.length; i++) {
-               if(elem.attributes[i].nodeName.toLowerCase() != "_realname") {
-                 result += " " + elem.attributes[i].nodeName.toLowerCase() +
-                "='" + elem.attributes[i].value
-                    .replace(/&/g, "&amp;")
-                       .replace(/\'/g, "&apos;")
-                       .replace(/</g, "&lt;") + "'";
-               }
-        }
-
-        if (elem._childNodes.length > 0) {
-            result += ">";
-            for (i = 0; i < elem._childNodes.length; i++) {
-                child = elem._childNodes[i];
-                if (child.nodeType == Strophe.ElementType.NORMAL) {
-                    // normal element, so recurse
-                    result += Strophe.serialize(child);
-                } else if (child.nodeType == Strophe.ElementType.TEXT) {
-                    // text element
-                    result += child.nodeValue;
-                }
-            }
-            result += "</" + nodeName.toLowerCase() + ">";
-        } else {
-            result += "/>";
-        }
-
-        return result;
-    },
-
-    /** PrivateVariable: _requestId
-     *  _Private_ variable that keeps track of the request ids for
-     *  connections.
-     */
-    _requestId: 0,
-
-    /** PrivateVariable: Strophe.connectionPlugins
-     *  _Private_ variable Used to store plugin names that need
-     *  initialization on Strophe.Connection construction.
-     */
-    _connectionPlugins: {},
-
-    /** Function: addConnectionPlugin
-     *  Extends the Strophe.Connection object with the given plugin.
-     *
-     *  Paramaters:
-     *    (String) name - The name of the extension.
-     *    (Object) ptype - The plugin's prototype.
-     */
-    addConnectionPlugin: function (name, ptype)
-    {
-        Strophe._connectionPlugins[name] = ptype;
-    }
-};
-
-/** Class: Strophe.Builder
- *  XML DOM builder.
- *
- *  This object provides an interface similar to JQuery but for building
- *  DOM element easily and rapidly.  All the functions except for toString()
- *  and tree() return the object, so calls can be chained.  Here's an
- *  example using the $iq() builder helper.
- *  > $iq({to: 'you', from: 'me', type: 'get', id: '1'})
- *  >     .c('query', {xmlns: 'strophe:example'})
- *  >     .c('example')
- *  >     .toString()
- *  The above generates this XML fragment
- *  > <iq to='you' from='me' type='get' id='1'>
- *  >   <query xmlns='strophe:example'>
- *  >     <example/>
- *  >   </query>
- *  > </iq>
- *  The corresponding DOM manipulations to get a similar fragment would be
- *  a lot more tedious and probably involve several helper variables.
- *
- *  Since adding children makes new operations operate on the child, up()
- *  is provided to traverse up the tree.  To add two children, do
- *  > builder.c('child1', ...).up().c('child2', ...)
- *  The next operation on the Builder will be relative to the second child.
- */
-
-/** Constructor: Strophe.Builder
- *  Create a Strophe.Builder object.
- *
- *  The attributes should be passed in object notation.  For example
- *  > var b = new Builder('message', {to: 'you', from: 'me'});
- *  or
- *  > var b = new Builder('messsage', {'xml:lang': 'en'});
- *
- *  Parameters:
- *    (String) name - The name of the root element.
- *    (Object) attrs - The attributes for the root element in object notation.
- *
- *  Returns:
- *    A new Strophe.Builder.
- */
-Strophe.Builder = function (name, attrs)
-{
-    // Set correct namespace for jabber:client elements
-    if (name == "presence" || name == "message" || name == "iq") {
-        if (attrs && !attrs.xmlns) {
-            attrs.xmlns = Strophe.NS.CLIENT;
-        } else if (!attrs) {
-            attrs = {xmlns: Strophe.NS.CLIENT};
-        }
-    }
-
-    // Holds the tree being built.
-    this.nodeTree = Strophe.xmlElement(name, attrs);
-
-    // Points to the current operation node.
-    this.node = this.nodeTree;
-};
-
-Strophe.Builder.prototype = {
-    /** Function: tree
-     *  Return the DOM tree.
-     *
-     *  This function returns the current DOM tree as an element object.  This
-     *  is suitable for passing to functions like Strophe.Connection.send().
-     *
-     *  Returns:
-     *    The DOM tree as a element object.
-     */
-    tree: function ()
-    {
-        return this.nodeTree;
-    },
-
-    /** Function: toString
-     *  Serialize the DOM tree to a String.
-     *
-     *  This function returns a string serialization of the current DOM
-     *  tree.  It is often used internally to pass data to a
-     *  Strophe.Request object.
-     *
-     *  Returns:
-     *    The serialized DOM tree in a String.
-     */
-    toString: function ()
-    {
-        return Strophe.serialize(this.nodeTree);
-    },
-
-    /** Function: up
-     *  Make the current parent element the new current element.
-     *
-     *  This function is often used after c() to traverse back up the tree.
-     *  For example, to add two children to the same element
-     *  > builder.c('child1', {}).up().c('child2', {});
-     *
-     *  Returns:
-     *    The Stophe.Builder object.
-     */
-    up: function ()
-    {
-        this.node = this.node.parentNode;
-        return this;
-    },
-
-    /** Function: attrs
-     *  Add or modify attributes of the current element.
-     *
-     *  The attributes should be passed in object notation.  This function
-     *  does not move the current element pointer.
-     *
-     *  Parameters:
-     *    (Object) moreattrs - The attributes to add/modify in object notation.
-     *
-     *  Returns:
-     *    The Strophe.Builder object.
-     */
-    attrs: function (moreattrs)
-    {
-        for (var k in moreattrs) {
-            if (moreattrs.hasOwnProperty(k)) {
-                this.node.setAttribute(k, moreattrs[k]);
-            }
-        }
-        return this;
-    },
-
-    /** Function: c
-     *  Add a child to the current element and make it the new current
-     *  element.
-     *
-     *  This function moves the current element pointer to the child.  If you
-     *  need to add another child, it is necessary to use up() to go back
-     *  to the parent in the tree.
-     *
-     *  Parameters:
-     *    (String) name - The name of the child.
-     *    (Object) attrs - The attributes of the child in object notation.
-     *
-     *  Returns:
-     *    The Strophe.Builder object.
-     */
-    c: function (name, attrs)
-    {
-        var child = Strophe.xmlElement(name, attrs);
-        this.node.appendChild(child);
-        this.node = child;
-        return this;
-    },
-
-    /** Function: cnode
-     *  Add a child to the current element and make it the new current
-     *  element.
-     *
-     *  This function is the same as c() except that instead of using a
-     *  name and an attributes object to create the child it uses an
-     *  existing DOM element object.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - A DOM element.
-     *
-     *  Returns:
-     *    The Strophe.Builder object.
-     */
-    cnode: function (elem)
-    {
-        var xmlGen = Strophe.xmlGenerator();
-        var newElem = xmlGen.importNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);
-        this.node.appendChild(newElem);
-        this.node = newElem;
-        return this;
-    },
-
-    /** Function: t
-     *  Add a child text element.
-     *
-     *  This *does not* make the child the new current element since there
-     *  are no children of text elements.
-     *
-     *  Parameters:
-     *    (String) text - The text data to append to the current element.
-     *
-     *  Returns:
-     *    The Strophe.Builder object.
-     */
-    t: function (text)
-    {
-        var child = Strophe.xmlTextNode(text);
-        this.node.appendChild(child);
-        return this;
-    }
-};
-
-
-/** PrivateClass: Strophe.Handler
- *  _Private_ helper class for managing stanza handlers.
- *
- *  A Strophe.Handler encapsulates a user provided callback function to be
- *  executed when matching stanzas are received by the connection.
- *  Handlers can be either one-off or persistant depending on their
- *  return value. Returning true will cause a Handler to remain active, and
- *  returning false will remove the Handler.
- *
- *  Users will not use Strophe.Handler objects directly, but instead they
- *  will use Strophe.Connection.addHandler() and
- *  Strophe.Connection.deleteHandler().
- */
-
-/** PrivateConstructor: Strophe.Handler
- *  Create and initialize a new Strophe.Handler.
- *
- *  Parameters:
- *    (Function) handler - A function to be executed when the handler is run.
- *    (String) ns - The namespace to match.
- *    (String) name - The element name to match.
- *    (String) type - The element type to match.
- *    (String) id - The element id attribute to match.
- *    (String) from - The element from attribute to match.
- *    (Object) options - Handler options
- *
- *  Returns:
- *    A new Strophe.Handler object.
- */
-Strophe.Handler = function (handler, ns, name, type, id, from, options)
-{
-    this.handler = handler;
-    this.ns = ns;
-    this.name = name;
-    this.type = type;
-    this.id = id;
-    this.options = options || {matchbare: false};
-
-    // default matchBare to false if undefined
-    if (!this.options.matchBare) {
-        this.options.matchBare = false;
-    }
-
-    if (this.options.matchBare) {
-        this.from = from ? Strophe.getBareJidFromJid(from) : null;
-    } else {
-        this.from = from;
-    }
-
-    // whether the handler is a user handler or a system handler
-    this.user = true;
-};
-
-Strophe.Handler.prototype = {
-    /** PrivateFunction: isMatch
-     *  Tests if a stanza matches the Strophe.Handler.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The XML element to test.
-     *
-     *  Returns:
-     *    true if the stanza matches and false otherwise.
-     */
-    isMatch: function (elem)
-    {
-        var nsMatch;
-        var from = null;
-
-        if (this.options.matchBare) {
-            from = Strophe.getBareJidFromJid(elem.getAttribute('from'));
-        } else {
-            from = elem.getAttribute('from');
-        }
-
-        nsMatch = false;
-        if (!this.ns) {
-            nsMatch = true;
-        } else {
-            var that = this;
-            Strophe.forEachChild(elem, null, function (elem) {
-                if (elem.getAttribute("xmlns") == that.ns) {
-                    nsMatch = true;
-                }
-            });
-
-            nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
-        }
-
-        if (nsMatch &&
-            (!this.name || Strophe.isTagEqual(elem, this.name)) &&
-            (!this.type || elem.getAttribute("type") == this.type) &&
-            (!this.id || elem.getAttribute("id") == this.id) &&
-            (!this.from || from == this.from)) {
-                return true;
-        }
-
-        return false;
-    },
-
-    /** PrivateFunction: run
-     *  Run the callback on a matching stanza.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The DOM element that triggered the
-     *      Strophe.Handler.
-     *
-     *  Returns:
-     *    A boolean indicating if the handler should remain active.
-     */
-    run: function (elem)
-    {
-        var result = null;
-        try {
-            result = this.handler(elem);
-        } catch (e) {
-            if (e.sourceURL) {
-                Strophe.fatal("error: " + this.handler +
-                              " " + e.sourceURL + ":" +
-                              e.line + " - " + e.name + ": " + e.message);
-            } else if (e.fileName) {
-                if (typeof(console) != "undefined") {
-                    console.trace();
-                    console.error(this.handler, " - error - ", e, e.message);
-                }
-                Strophe.fatal("error: " + this.handler + " " +
-                              e.fileName + ":" + e.lineNumber + " - " +
-                              e.name + ": " + e.message);
-            } else {
-                Strophe.fatal("error: " + this.handler);
-            }
-
-            throw e;
-        }
-
-        return result;
-    },
-
-    /** PrivateFunction: toString
-     *  Get a String representation of the Strophe.Handler object.
-     *
-     *  Returns:
-     *    A String.
-     */
-    toString: function ()
-    {
-        return "{Handler: " + this.handler + "(" + this.name + "," +
-            this.id + "," + this.ns + ")}";
-    }
-};
-
-/** PrivateClass: Strophe.TimedHandler
- *  _Private_ helper class for managing timed handlers.
- *
- *  A Strophe.TimedHandler encapsulates a user provided callback that
- *  should be called after a certain period of time or at regular
- *  intervals.  The return value of the callback determines whether the
- *  Strophe.TimedHandler will continue to fire.
- *
- *  Users will not use Strophe.TimedHandler objects directly, but instead
- *  they will use Strophe.Connection.addTimedHandler() and
- *  Strophe.Connection.deleteTimedHandler().
- */
-
-/** PrivateConstructor: Strophe.TimedHandler
- *  Create and initialize a new Strophe.TimedHandler object.
- *
- *  Parameters:
- *    (Integer) period - The number of milliseconds to wait before the
- *      handler is called.
- *    (Function) handler - The callback to run when the handler fires.  This
- *      function should take no arguments.
- *
- *  Returns:
- *    A new Strophe.TimedHandler object.
- */
-Strophe.TimedHandler = function (period, handler)
-{
-    this.period = period;
-    this.handler = handler;
-
-    this.lastCalled = new Date().getTime();
-    this.user = true;
-};
-
-Strophe.TimedHandler.prototype = {
-    /** PrivateFunction: run
-     *  Run the callback for the Strophe.TimedHandler.
-     *
-     *  Returns:
-     *    true if the Strophe.TimedHandler should be called again, and false
-     *      otherwise.
-     */
-    run: function ()
-    {
-        this.lastCalled = new Date().getTime();
-        return this.handler();
-    },
-
-    /** PrivateFunction: reset
-     *  Reset the last called time for the Strophe.TimedHandler.
-     */
-    reset: function ()
-    {
-        this.lastCalled = new Date().getTime();
-    },
-
-    /** PrivateFunction: toString
-     *  Get a string representation of the Strophe.TimedHandler object.
-     *
-     *  Returns:
-     *    The string representation.
-     */
-    toString: function ()
-    {
-        return "{TimedHandler: " + this.handler + "(" + this.period +")}";
-    }
-};
-
-/** PrivateClass: Strophe.Request
- *  _Private_ helper class that provides a cross implementation abstraction
- *  for a BOSH related XMLHttpRequest.
- *
- *  The Strophe.Request class is used internally to encapsulate BOSH request
- *  information.  It is not meant to be used from user's code.
- */
-
-/** PrivateConstructor: Strophe.Request
- *  Create and initialize a new Strophe.Request object.
- *
- *  Parameters:
- *    (XMLElement) elem - The XML data to be sent in the request.
- *    (Function) func - The function that will be called when the
- *      XMLHttpRequest readyState changes.
- *    (Integer) rid - The BOSH rid attribute associated with this request.
- *    (Integer) sends - The number of times this same request has been
- *      sent.
- */
-Strophe.Request = function (elem, func, rid, sends)
-{
-    this.id = ++Strophe._requestId;
-    this.xmlData = elem;
-    this.data = Strophe.serialize(elem);
-    // save original function in case we need to make a new request
-    // from this one.
-    this.origFunc = func;
-    this.func = func;
-    this.rid = rid;
-    this.date = NaN;
-    this.sends = sends || 0;
-    this.abort = false;
-    this.dead = null;
-    this.age = function () {
-        if (!this.date) { return 0; }
-        var now = new Date();
-        return (now - this.date) / 1000;
-    };
-    this.timeDead = function () {
-        if (!this.dead) { return 0; }
-        var now = new Date();
-        return (now - this.dead) / 1000;
-    };
-    this.xhr = this._newXHR();
-};
-
-Strophe.Request.prototype = {
-    /** PrivateFunction: getResponse
-     *  Get a response from the underlying XMLHttpRequest.
-     *
-     *  This function attempts to get a response from the request and checks
-     *  for errors.
-     *
-     *  Throws:
-     *    "parsererror" - A parser error occured.
-     *
-     *  Returns:
-     *    The DOM element tree of the response.
-     */
-    getResponse: function ()
-    {
-		// console.log("getResponse:", this.xhr.responseXML, ":", this.xhr.responseText);
-
-        var node = null;
-        if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
-            node = this.xhr.responseXML.documentElement;
-            if (node.tagName == "parsererror") {
-                Strophe.error("invalid response received");
-                Strophe.error("responseText: " + this.xhr.responseText);
-                Strophe.error("responseXML: " +
-                              Strophe.serialize(this.xhr.responseXML));
-                throw "parsererror";
-            }
-        } else if (this.xhr.responseText) {
-			// Hack for node.
-			var _div = document.createElement("div");
-			_div.innerHTML = this.xhr.responseText;
-			node = _div._childNodes[0];
-
-            Strophe.error("invalid response received");
-            Strophe.error("responseText: " + this.xhr.responseText);
-            Strophe.error("responseXML: " +
-                          Strophe.serialize(this.xhr.responseXML));
-        }
-
-        return node;
-    },
-
-    /** PrivateFunction: _newXHR
-     *  _Private_ helper function to create XMLHttpRequests.
-     *
-     *  This function creates XMLHttpRequests across all implementations.
-     *
-     *  Returns:
-     *    A new XMLHttpRequest.
-     */
-    _newXHR: function ()
-    {
-        var xhr = null;
-        if (window.XMLHttpRequest) {
-            xhr = new XMLHttpRequest();
-            if (xhr.overrideMimeType) {
-                xhr.overrideMimeType("text/xml");
-            }
-        } else if (window.ActiveXObject) {
-            xhr = new ActiveXObject("Microsoft.XMLHTTP");
-        }
-
-        // use Function.bind() to prepend ourselves as an argument
-        xhr.onreadystatechange = this.func.bind(null, this);
-
-        return xhr;
-    }
-};
-
-/** Class: Strophe.Connection
- *  XMPP Connection manager.
- *
- *  Thie class is the main part of Strophe.  It manages a BOSH connection
- *  to an XMPP server and dispatches events to the user callbacks as
- *  data arrives.  It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
- *  authentication.
- *
- *  After creating a Strophe.Connection object, the user will typically
- *  call connect() with a user supplied callback to handle connection level
- *  events like authentication failure, disconnection, or connection
- *  complete.
- *
- *  The user will also have several event handlers defined by using
- *  addHandler() and addTimedHandler().  These will allow the user code to
- *  respond to interesting stanzas or do something periodically with the
- *  connection.  These handlers will be active once authentication is
- *  finished.
- *
- *  To send data to the connection, use send().
- */
-
-/** Constructor: Strophe.Connection
- *  Create and initialize a Strophe.Connection object.
- *
- *  Parameters:
- *    (String) service - The BOSH service URL.
- *
- *  Returns:
- *    A new Strophe.Connection object.
- */
-Strophe.Connection = function (service)
-{
-    /* The path to the httpbind service. */
-    this.service = service;
-    /* The connected JID. */
-    this.jid = "";
-    /* request id for body tags */
-    this.rid = Math.floor(Math.random() * 4294967295);
-    /* The current session ID. */
-    this.sid = null;
-    this.streamId = null;
-    /* stream:features */
-    this.features = null;
-
-    // SASL
-    this.do_session = false;
-    this.do_bind = false;
-
-    // handler lists
-    this.timedHandlers = [];
-    this.handlers = [];
-    this.removeTimeds = [];
-    this.removeHandlers = [];
-    this.addTimeds = [];
-    this.addHandlers = [];
-
-    this._idleTimeout = null;
-    this._disconnectTimeout = null;
-
-    this.authenticated = false;
-    this.disconnecting = false;
-    this.connected = false;
-
-    this.errors = 0;
-
-    this.paused = false;
-
-    // default BOSH values
-    this.hold = 1;
-    this.wait = 60;
-    this.window = 5;
-
-    this._data = [];
-    this._requests = [];
-    this._uniqueId = Math.round(Math.random() * 10000);
-
-    this._sasl_success_handler = null;
-    this._sasl_failure_handler = null;
-    this._sasl_challenge_handler = null;
-
-    // setup onIdle callback every 1/10th of a second
-    this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
-
-    // initialize plugins
-    for (var k in Strophe._connectionPlugins) {
-        if (Strophe._connectionPlugins.hasOwnProperty(k)) {
-	    var ptype = Strophe._connectionPlugins[k];
-            // jslint complaints about the below line, but this is fine
-            var F = function () {};
-            F.prototype = ptype;
-            this[k] = new F();
-	    this[k].init(this);
-        }
-    }
-};
-
-Strophe.Connection.prototype = {
-    /** Function: reset
-     *  Reset the connection.
-     *
-     *  This function should be called after a connection is disconnected
-     *  before that connection is reused.
-     */
-    reset: function ()
-    {
-        this.rid = Math.floor(Math.random() * 4294967295);
-
-        this.sid = null;
-        this.streamId = null;
-
-        // SASL
-        this.do_session = false;
-        this.do_bind = false;
-
-        // handler lists
-        this.timedHandlers = [];
-        this.handlers = [];
-        this.removeTimeds = [];
-        this.removeHandlers = [];
-        this.addTimeds = [];
-        this.addHandlers = [];
-
-        this.authenticated = false;
-        this.disconnecting = false;
-        this.connected = false;
-
-        this.errors = 0;
-
-        this._requests = [];
-        this._uniqueId = Math.round(Math.random()*10000);
-    },
-
-    /** Function: pause
-     *  Pause the request manager.
-     *
-     *  This will prevent Strophe from sending any more requests to the
-     *  server.  This is very useful for temporarily pausing while a lot
-     *  of send() calls are happening quickly.  This causes Strophe to
-     *  send the data in a single request, saving many request trips.
-     */
-    pause: function ()
-    {
-        this.paused = true;
-    },
-
-    /** Function: resume
-     *  Resume the request manager.
-     *
-     *  This resumes after pause() has been called.
-     */
-    resume: function ()
-    {
-        this.paused = false;
-    },
-
-    /** Function: getUniqueId
-     *  Generate a unique ID for use in <iq/> elements.
-     *
-     *  All <iq/> stanzas are required to have unique id attributes.  This
-     *  function makes creating these easy.  Each connection instance has
-     *  a counter which starts from zero, and the value of this counter
-     *  plus a colon followed by the suffix becomes the unique id. If no
-     *  suffix is supplied, the counter is used as the unique id.
-     *
-     *  Suffixes are used to make debugging easier when reading the stream
-     *  data, and their use is recommended.  The counter resets to 0 for
-     *  every new connection for the same reason.  For connections to the
-     *  same server that authenticate the same way, all the ids should be
-     *  the same, which makes it easy to see changes.  This is useful for
-     *  automated testing as well.
-     *
-     *  Parameters:
-     *    (String) suffix - A optional suffix to append to the id.
-     *
-     *  Returns:
-     *    A unique string to be used for the id attribute.
-     */
-    getUniqueId: function (suffix)
-    {
-        if (typeof(suffix) == "string" || typeof(suffix) == "number") {
-            return ++this._uniqueId + ":" + suffix;
-        } else {
-            return ++this._uniqueId + "";
-        }
-    },
-
-    /** Function: connect
-     *  Starts the connection process.
-     *
-     *  As the connection process proceeds, the user supplied callback will
-     *  be triggered multiple times with status updates.  The callback
-     *  should take two arguments - the status code and the error condition.
-     *
-     *  The status code will be one of the values in the Strophe.Status
-     *  constants.  The error condition will be one of the conditions
-     *  defined in RFC 3920 or the condition 'strophe-parsererror'.
-     *
-     *  Please see XEP 124 for a more detailed explanation of the optional
-     *  parameters below.
-     *
-     *  Parameters:
-     *    (String) jid - The user's JID.  This may be a bare JID,
-     *      or a full JID.  If a node is not supplied, SASL ANONYMOUS
-     *      authentication will be attempted.
-     *    (String) pass - The user's password.
-     *    (Function) callback The connect callback function.
-     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
-     *      time the server will wait before returning an empty result for
-     *      a request.  The default setting of 60 seconds is recommended.
-     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
-     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
-     *      number of connections the server will hold at one time.  This
-     *      should almost always be set to 1 (the default).
-     */
-    connect: function (jid, pass, callback, wait, hold, route)
-    {
-        this.jid = jid;
-        this.pass = pass;
-        this.connect_callback = callback;
-        this.disconnecting = false;
-        this.connected = false;
-        this.authenticated = false;
-        this.errors = 0;
-
-        this.wait = wait || this.wait;
-        this.hold = hold || this.hold;
-
-        // parse jid for domain and resource
-        this.domain = Strophe.getDomainFromJid(this.jid);
-
-        // build the body tag
-		var body_attrs = {
-            to: this.domain,
-            "xml:lang": "en",
-            wait: this.wait,
-            hold: this.hold,
-            content: "text/xml; charset=utf-8",
-            ver: "1.6",
-            "xmpp:version": "1.0",
-            "xmlns:xmpp": Strophe.NS.BOSH
-        };
-		if (route) {
-			body_attrs.route = route;
-		}
-
-        var body = this._buildBody().attrs(body_attrs);
-
-        this._changeConnectStatus(Strophe.Status.CONNECTING, null);
-
-        this._requests.push(
-            new Strophe.Request(body.tree(),
-                                this._onRequestStateChange.bind(
-                                    this, this._connect_cb.bind(this)),
-                                body.tree().getAttribute("rid")));
-        this._throttledRequestHandler();
-    },
-
-    /** Function: attach
-     *  Attach to an already created and authenticated BOSH session.
-     *
-     *  This function is provided to allow Strophe to attach to BOSH
-     *  sessions which have been created externally, perhaps by a Web
-     *  application.  This is often used to support auto-login type features
-     *  without putting user credentials into the page.
-     *
-     *  Parameters:
-     *    (String) jid - The full JID that is bound by the session.
-     *    (String) sid - The SID of the BOSH session.
-     *    (String) rid - The current RID of the BOSH session.  This RID
-     *      will be used by the next request.
-     *    (Function) callback The connect callback function.
-     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
-     *      time the server will wait before returning an empty result for
-     *      a request.  The default setting of 60 seconds is recommended.
-     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
-     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
-     *      number of connections the server will hold at one time.  This
-     *      should almost always be set to 1 (the default).
-     *    (Integer) wind - The optional HTTBIND window value.  This is the
-     *      allowed range of request ids that are valid.  The default is 5.
-     */
-    attach: function (jid, sid, rid, callback, wait, hold, wind)
-    {
-        this.jid = jid;
-        this.sid = sid;
-        this.rid = rid;
-        this.connect_callback = callback;
-
-        this.domain = Strophe.getDomainFromJid(this.jid);
-
-        this.authenticated = true;
-        this.connected = true;
-
-        this.wait = wait || this.wait;
-        this.hold = hold || this.hold;
-        this.window = wind || this.window;
-
-        this._changeConnectStatus(Strophe.Status.ATTACHED, null);
-    },
-
-    /** Function: xmlInput
-     *  User overrideable function that receives XML data coming into the
-     *  connection.
-     *
-     *  The default function does nothing.  User code can override this with
-     *  > Strophe.Connection.xmlInput = function (elem) {
-     *  >   (user code)
-     *  > };
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The XML data received by the connection.
-     */
-    xmlInput: function (elem)
-    {
-        return;
-    },
-
-    /** Function: xmlOutput
-     *  User overrideable function that receives XML data sent to the
-     *  connection.
-     *
-     *  The default function does nothing.  User code can override this with
-     *  > Strophe.Connection.xmlOutput = function (elem) {
-     *  >   (user code)
-     *  > };
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The XMLdata sent by the connection.
-     */
-    xmlOutput: function (elem)
-    {
-        return;
-    },
-
-    /** Function: rawInput
-     *  User overrideable function that receives raw data coming into the
-     *  connection.
-     *
-     *  The default function does nothing.  User code can override this with
-     *  > Strophe.Connection.rawInput = function (data) {
-     *  >   (user code)
-     *  > };
-     *
-     *  Parameters:
-     *    (String) data - The data received by the connection.
-     */
-    rawInput: function (data)
-    {
-        return;
-    },
-
-    /** Function: rawOutput
-     *  User overrideable function that receives raw data sent to the
-     *  connection.
-     *
-     *  The default function does nothing.  User code can override this with
-     *  > Strophe.Connection.rawOutput = function (data) {
-     *  >   (user code)
-     *  > };
-     *
-     *  Parameters:
-     *    (String) data - The data sent by the connection.
-     */
-    rawOutput: function (data)
-    {
-        return;
-    },
-
-    /** Function: send
-     *  Send a stanza.
-     *
-     *  This function is called to push data onto the send queue to
-     *  go out over the wire.  Whenever a request is sent to the BOSH
-     *  server, all pending data is sent and the queue is flushed.
-     *
-     *  Parameters:
-     *    (XMLElement |
-     *     [XMLElement] |
-     *     Strophe.Builder) elem - The stanza to send.
-     */
-    send: function (elem)
-    {
-        if (elem === null) { return ; }
-        if (typeof(elem.sort) === "function") {
-            for (var i = 0; i < elem.length; i++) {
-                this._queueData(elem[i]);
-            }
-        } else if (typeof(elem.tree) === "function") {
-            this._queueData(elem.tree());
-        } else {
-            this._queueData(elem);
-        }
-
-        this._throttledRequestHandler();
-        clearTimeout(this._idleTimeout);
-        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
-    },
-
-    /** Function: flush
-     *  Immediately send any pending outgoing data.
-     *
-     *  Normally send() queues outgoing data until the next idle period
-     *  (100ms), which optimizes network use in the common cases when
-     *  several send()s are called in succession. flush() can be used to
-     *  immediately send all pending data.
-     */
-    flush: function ()
-    {
-        // cancel the pending idle period and run the idle function
-        // immediately
-        clearTimeout(this._idleTimeout);
-        this._onIdle();
-    },
-
-    /** Function: sendIQ
-     *  Helper function to send IQ stanzas.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The stanza to send.
-     *    (Function) callback - The callback function for a successful request.
-     *    (Function) errback - The callback function for a failed or timed
-     *      out request.  On timeout, the stanza will be null.
-     *    (Integer) timeout - The time specified in milliseconds for a
-     *      timeout to occur.
-     *
-     *  Returns:
-     *    The id used to send the IQ.
-    */
-    sendIQ: function(elem, callback, errback, timeout) {
-        var timeoutHandler = null;
-        var that = this;
-
-        if (typeof(elem.tree) === "function") {
-            elem = elem.tree();
-        }
-	var id = elem.getAttribute('id');
-
-	// inject id if not found
-	if (!id) {
-	    id = this.getUniqueId("sendIQ");
-	    elem.setAttribute("id", id);
-	}
-
-	var handler = this.addHandler(function (stanza) {
-	    // remove timeout handler if there is one
-            if (timeoutHandler) {
-                that.deleteTimedHandler(timeoutHandler);
-            }
-
-            var iqtype = stanza.getAttribute('type');
-	    if (iqtype == 'result') {
-		if (callback) {
-                    callback(stanza);
-                }
-	    } else if (iqtype == 'error') {
-		if (errback) {
-                    errback(stanza);
-                }
-	    } else {
-                throw {
-                    name: "StropheError",
-                    message: "Got bad IQ type of " + iqtype
-                };
-            }
-	}, null, 'iq', null, id);
-
-	// if timeout specified, setup timeout handler.
-	if (timeout) {
-	    timeoutHandler = this.addTimedHandler(timeout, function () {
-                // get rid of normal handler
-                that.deleteHandler(handler);
-
-	        // call errback on timeout with null stanza
-                if (errback) {
-		    errback(null);
-                }
-		return false;
-	    });
-	}
-
-	this.send(elem);
-
-	return id;
-    },
-
-    /** PrivateFunction: _queueData
-     *  Queue outgoing data for later sending.  Also ensures that the data
-     *  is a DOMElement.
-     */
-    _queueData: function (element) {
-        if (element === null ||
-            !element.tagName ||
-            !element._childNodes) {
-            throw {
-                name: "StropheError",
-                message: "Cannot queue non-DOMElement."
-            };
-        }
-
-        this._data.push(element);
-    },
-
-    /** PrivateFunction: _sendRestart
-     *  Send an xmpp:restart stanza.
-     */
-    _sendRestart: function ()
-    {
-        this._data.push("restart");
-
-        this._throttledRequestHandler();
-        clearTimeout(this._idleTimeout);
-        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
-    },
-
-    /** Function: addTimedHandler
-     *  Add a timed handler to the connection.
-     *
-     *  This function adds a timed handler.  The provided handler will
-     *  be called every period milliseconds until it returns false,
-     *  the connection is terminated, or the handler is removed.  Handlers
-     *  that wish to continue being invoked should return true.
-     *
-     *  Because of method binding it is necessary to save the result of
-     *  this function if you wish to remove a handler with
-     *  deleteTimedHandler().
-     *
-     *  Note that user handlers are not active until authentication is
-     *  successful.
-     *
-     *  Parameters:
-     *    (Integer) period - The period of the handler.
-     *    (Function) handler - The callback function.
-     *
-     *  Returns:
-     *    A reference to the handler that can be used to remove it.
-     */
-    addTimedHandler: function (period, handler)
-    {
-        var thand = new Strophe.TimedHandler(period, handler);
-        this.addTimeds.push(thand);
-        return thand;
-    },
-
-    /** Function: deleteTimedHandler
-     *  Delete a timed handler for a connection.
-     *
-     *  This function removes a timed handler from the connection.  The
-     *  handRef parameter is *not* the function passed to addTimedHandler(),
-     *  but is the reference returned from addTimedHandler().
-     *
-     *  Parameters:
-     *    (Strophe.TimedHandler) handRef - The handler reference.
-     */
-    deleteTimedHandler: function (handRef)
-    {
-        // this must be done in the Idle loop so that we don't change
-        // the handlers during iteration
-        this.removeTimeds.push(handRef);
-    },
-
-    /** Function: addHandler
-     *  Add a stanza handler for the connection.
-     *
-     *  This function adds a stanza handler to the connection.  The
-     *  handler callback will be called for any stanza that matches
-     *  the parameters.  Note that if multiple parameters are supplied,
-     *  they must all match for the handler to be invoked.
-     *
-     *  The handler will receive the stanza that triggered it as its argument.
-     *  The handler should return true if it is to be invoked again;
-     *  returning false will remove the handler after it returns.
-     *
-     *  As a convenience, the ns parameters applies to the top level element
-     *  and also any of its immediate children.  This is primarily to make
-     *  matching /iq/query elements easy.
-     *
-     *  The options argument contains handler matching flags that affect how
-     *  matches are determined. Currently the only flag is matchBare (a
-     *  boolean). When matchBare is true, the from parameter and the from
-     *  attribute on the stanza will be matched as bare JIDs instead of
-     *  full JIDs. To use this, pass {matchBare: true} as the value of
-     *  options. The default value for matchBare is false.
-     *
-     *  The return value should be saved if you wish to remove the handler
-     *  with deleteHandler().
-     *
-     *  Parameters:
-     *    (Function) handler - The user callback.
-     *    (String) ns - The namespace to match.
-     *    (String) name - The stanza name to match.
-     *    (String) type - The stanza type attribute to match.
-     *    (String) id - The stanza id attribute to match.
-     *    (String) from - The stanza from attribute to match.
-     *    (String) options - The handler options
-     *
-     *  Returns:
-     *    A reference to the handler that can be used to remove it.
-     */
-    addHandler: function (handler, ns, name, type, id, from, options)
-    {
-        var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
-        this.addHandlers.push(hand);
-        return hand;
-    },
-
-    /** Function: deleteHandler
-     *  Delete a stanza handler for a connection.
-     *
-     *  This function removes a stanza handler from the connection.  The
-     *  handRef parameter is *not* the function passed to addHandler(),
-     *  but is the reference returned from addHandler().
-     *
-     *  Parameters:
-     *    (Strophe.Handler) handRef - The handler reference.
-     */
-    deleteHandler: function (handRef)
-    {
-        // this must be done in the Idle loop so that we don't change
-        // the handlers during iteration
-        this.removeHandlers.push(handRef);
-    },
-
-    /** Function: disconnect
-     *  Start the graceful disconnection process.
-     *
-     *  This function starts the disconnection process.  This process starts
-     *  by sending unavailable presence and sending BOSH body of type
-     *  terminate.  A timeout handler makes sure that disconnection happens
-     *  even if the BOSH server does not respond.
-     *
-     *  The user supplied connection callback will be notified of the
-     *  progress as this process happens.
-     *
-     *  Parameters:
-     *    (String) reason - The reason the disconnect is occuring.
-     */
-    disconnect: function (reason)
-    {
-        this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
-
-        Strophe.info("Disconnect was called because: " + reason);
-        if (this.connected) {
-            // setup timeout handler
-            this._disconnectTimeout = this._addSysTimedHandler(
-                3000, this._onDisconnectTimeout.bind(this));
-            this._sendTerminate();
-        }
-    },
-
-    /** PrivateFunction: _changeConnectStatus
-     *  _Private_ helper function that makes sure plugins and the user's
-     *  callback are notified of connection status changes.
-     *
-     *  Parameters:
-     *    (Integer) status - the new connection status, one of the values
-     *      in Strophe.Status
-     *    (String) condition - the error condition or null
-     */
-    _changeConnectStatus: function (status, condition)
-    {
-        // notify all plugins listening for status changes
-        for (var k in Strophe._connectionPlugins) {
-            if (Strophe._connectionPlugins.hasOwnProperty(k)) {
-                var plugin = this[k];
-                if (plugin.statusChanged) {
-                    try {
-                        plugin.statusChanged(status, condition);
-                    } catch (err) {
-                        Strophe.error("" + k + " plugin caused an exception " +
-                                      "changing status: " + err);
-                    }
-                }
-            }
-        }
-
-        // notify the user's callback
-        if (this.connect_callback) {
-            try {
-                this.connect_callback(status, condition);
-            } catch (e) {
-                Strophe.error("User connection callback caused an " +
-                              "exception: " + e);
-            }
-        }
-    },
-
-    /** PrivateFunction: _buildBody
-     *  _Private_ helper function to generate the <body/> wrapper for BOSH.
-     *
-     *  Returns:
-     *    A Strophe.Builder with a <body/> element.
-     */
-    _buildBody: function ()
-    {
-        var bodyWrap = $build('body', {
-            rid: this.rid++,
-            xmlns: Strophe.NS.HTTPBIND
-        });
-
-        if (this.sid !== null) {
-            bodyWrap.attrs({sid: this.sid});
-        }
-
-        return bodyWrap;
-    },
-
-    /** PrivateFunction: _removeRequest
-     *  _Private_ function to remove a request from the queue.
-     *
-     *  Parameters:
-     *    (Strophe.Request) req - The request to remove.
-     */
-    _removeRequest: function (req)
-    {
-        Strophe.debug("removing request");
-
-        var i;
-        for (i = this._requests.length - 1; i >= 0; i--) {
-            if (req == this._requests[i]) {
-                this._requests.splice(i, 1);
-            }
-        }
-
-        // IE6 fails on setting to null, so set to empty function
-        req.xhr.onreadystatechange = function () {};
-
-        this._throttledRequestHandler();
-    },
-
-    /** PrivateFunction: _restartRequest
-     *  _Private_ function to restart a request that is presumed dead.
-     *
-     *  Parameters:
-     *    (Integer) i - The index of the request in the queue.
-     */
-    _restartRequest: function (i)
-    {
-        var req = this._requests[i];
-        if (req.dead === null) {
-            req.dead = new Date();
-        }
-
-        this._processRequest(i);
-    },
-
-    /** PrivateFunction: _processRequest
-     *  _Private_ function to process a request in the queue.
-     *
-     *  This function takes requests off the queue and sends them and
-     *  restarts dead requests.
-     *
-     *  Parameters:
-     *    (Integer) i - The index of the request in the queue.
-     */
-    _processRequest: function (i)
-    {
-        var req = this._requests[i];
-        var reqStatus = -1;
-
-        try {
-            if (req.xhr.readyState == 4) {
-                reqStatus = req.xhr.status;
-            }
-        } catch (e) {
-            Strophe.error("caught an error in _requests[" + i +
-                          "], reqStatus: " + reqStatus);
-        }
-
-        if (typeof(reqStatus) == "undefined") {
-            reqStatus = -1;
-        }
-
-        // make sure we limit the number of retries
-        if (req.sends > 5) {
-            this._onDisconnectTimeout();
-            return;
-        }
-
-        var time_elapsed = req.age();
-        var primaryTimeout = (!isNaN(time_elapsed) &&
-                              time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait));
-        var secondaryTimeout = (req.dead !== null &&
-                                req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait));
-        var requestCompletedWithServerError = (req.xhr.readyState == 4 &&
-                                               (reqStatus < 1 ||
-                                                reqStatus >= 500));
-        if (primaryTimeout || secondaryTimeout ||
-            requestCompletedWithServerError) {
-            if (secondaryTimeout) {
-                Strophe.error("Request " +
-                              this._requests[i].id +
-                              " timed out (secondary), restarting");
-            }
-            req.abort = true;
-            req.xhr.abort();
-            // setting to null fails on IE6, so set to empty function
-            req.xhr.onreadystatechange = function () {};
-            this._requests[i] = new Strophe.Request(req.xmlData,
-                                                    req.origFunc,
-                                                    req.rid,
-                                                    req.sends);
-            req = this._requests[i];
-        }
-
-        if (req.xhr.readyState === 0) {
-            Strophe.debug("request id " + req.id +
-                          "." + req.sends + " posting");
-
-            req.date = new Date();
-            try {
-                req.xhr.open("POST", this.service, true);
-            } catch (e2) {
-                Strophe.error("XHR open failed.");
-                if (!this.connected) {
-                    this._changeConnectStatus(Strophe.Status.CONNFAIL,
-                                              "bad-service");
-                }
-                this.disconnect();
-                return;
-            }
-
-            // Fires the XHR request -- may be invoked immediately
-            // or on a gradually expanding retry window for reconnects
-            var sendFunc = function () {
-                req.xhr.send(req.data);
-            };
-
-            // Implement progressive backoff for reconnects --
-            // First retry (send == 1) should also be instantaneous
-            if (req.sends > 1) {
-                // Using a cube of the retry number creats a nicely
-                // expanding retry window
-                var backoff = Math.pow(req.sends, 3) * 1000;
-                setTimeout(sendFunc, backoff);
-            } else {
-                sendFunc();
-            }
-
-            req.sends++;
-
-            this.xmlOutput(req.xmlData);
-            this.rawOutput(req.data);
-        } else {
-            Strophe.debug("_processRequest: " +
-                          (i === 0 ? "first" : "second") +
-                          " request has readyState of " +
-                          req.xhr.readyState);
-        }
-    },
-
-    /** PrivateFunction: _throttledRequestHandler
-     *  _Private_ function to throttle requests to the connection window.
-     *
-     *  This function makes sure we don't send requests so fast that the
-     *  request ids overflow the connection window in the case that one
-     *  request died.
-     */
-    _throttledRequestHandler: function ()
-    {
-        if (!this._requests) {
-            Strophe.debug("_throttledRequestHandler called with " +
-                          "undefined requests");
-        } else {
-            Strophe.debug("_throttledRequestHandler called with " +
-                          this._requests.length + " requests");
-        }
-
-        if (!this._requests || this._requests.length === 0) {
-            return;
-        }
-
-        if (this._requests.length > 0) {
-            this._processRequest(0);
-        }
-
-        if (this._requests.length > 1 &&
-            Math.abs(this._requests[0].rid -
-                     this._requests[1].rid) < this.window) {
-            this._processRequest(1);
-        }
-    },
-
-    /** PrivateFunction: _onRequestStateChange
-     *  _Private_ handler for Strophe.Request state changes.
-     *
-     *  This function is called when the XMLHttpRequest readyState changes.
-     *  It contains a lot of error handling logic for the many ways that
-     *  requests can fail, and calls the request callback when requests
-     *  succeed.
-     *
-     *  Parameters:
-     *    (Function) func - The handler for the request.
-     *    (Strophe.Request) req - The request that is changing readyState.
-     */
-    _onRequestStateChange: function (func, req)
-    {
-        Strophe.debug("request id " + req.id +
-                      "." + req.sends + " state changed to " +
-                      req.xhr.readyState);
-
-        if (req.abort) {
-            req.abort = false;
-            return;
-        }
-
-        // request complete
-        var reqStatus;
-        if (req.xhr.readyState == 4) {
-            reqStatus = 0;
-            try {
-                reqStatus = req.xhr.status;
-            } catch (e) {
-                // ignore errors from undefined status attribute.  works
-                // around a browser bug
-            }
-
-            if (typeof(reqStatus) == "undefined") {
-                reqStatus = 0;
-            }
-
-            if (this.disconnecting) {
-                if (reqStatus >= 400) {
-                    this._hitError(reqStatus);
-                    return;
-                }
-            }
-
-            var reqIs0 = (this._requests[0] == req);
-            var reqIs1 = (this._requests[1] == req);
-
-            if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
-                // remove from internal queue
-                this._removeRequest(req);
-                Strophe.debug("request id " +
-                              req.id +
-                              " should now be removed");
-            }
-
-            // request succeeded
-            if (reqStatus == 200) {
-                // if request 1 finished, or request 0 finished and request
-                // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
-                // restart the other - both will be in the first spot, as the
-                // completed request has been removed from the queue already
-                if (reqIs1 ||
-                    (reqIs0 && this._requests.length > 0 &&
-                     this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) {
-                    this._restartRequest(0);
-                }
-                // call handler
-                Strophe.debug("request id " +
-                              req.id + "." +
-                              req.sends + " got 200");
-                func(req);
-                this.errors = 0;
-            } else {
-                Strophe.error("request id " +
-                              req.id + "." +
-                              req.sends + " error " + reqStatus +
-                              " happened");
-                if (reqStatus === 0 ||
-                    (reqStatus >= 400 && reqStatus < 600) ||
-                    reqStatus >= 12000) {
-                    this._hitError(reqStatus);
-                    if (reqStatus >= 400 && reqStatus < 500) {
-                        this._changeConnectStatus(Strophe.Status.DISCONNECTING,
-                                                  null);
-                        this._doDisconnect();
-                    }
-                }
-            }
-
-            if (!((reqStatus > 0 && reqStatus < 500) ||
-                  req.sends > 5)) {
-                this._throttledRequestHandler();
-            }
-        }
-    },
-
-    /** PrivateFunction: _hitError
-     *  _Private_ function to handle the error count.
-     *
-     *  Requests are resent automatically until their error count reaches
-     *  5.  Each time an error is encountered, this function is called to
-     *  increment the count and disconnect if the count is too high.
-     *
-     *  Parameters:
-     *    (Integer) reqStatus - The request status.
-     */
-    _hitError: function (reqStatus)
-    {
-        this.errors++;
-        Strophe.warn("request errored, status: " + reqStatus +
-                     ", number of errors: " + this.errors);
-        if (this.errors > 4) {
-            this._onDisconnectTimeout();
-        }
-    },
-
-    /** PrivateFunction: _doDisconnect
-     *  _Private_ function to disconnect.
-     *
-     *  This is the last piece of the disconnection logic.  This resets the
-     *  connection and alerts the user's connection callback.
-     */
-    _doDisconnect: function ()
-    {
-        Strophe.info("_doDisconnect was called");
-        this.authenticated = false;
-        this.disconnecting = false;
-        this.sid = null;
-        this.streamId = null;
-        this.rid = Math.floor(Math.random() * 4294967295);
-
-        // tell the parent we disconnected
-        if (this.connected) {
-            this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
-            this.connected = false;
-        }
-
-        // delete handlers
-        this.handlers = [];
-        this.timedHandlers = [];
-        this.removeTimeds = [];
-        this.removeHandlers = [];
-        this.addTimeds = [];
-        this.addHandlers = [];
-    },
-
-    /** PrivateFunction: _dataRecv
-     *  _Private_ handler to processes incoming data from the the connection.
-     *
-     *  Except for _connect_cb handling the initial connection request,
-     *  this function handles the incoming data for all requests.  This
-     *  function also fires stanza handlers that match each incoming
-     *  stanza.
-     *
-     *  Parameters:
-     *    (Strophe.Request) req - The request that has data ready.
-     */
-    _dataRecv: function (req)
-    {
-        try {
-            var elem = req.getResponse();
-        } catch (e) {
-            if (e != "parsererror") { throw e; }
-            this.disconnect("strophe-parsererror");
-        }
-        if (elem === null) { return; }
-
-        this.xmlInput(elem);
-        this.rawInput(Strophe.serialize(elem));
-
-        // remove handlers scheduled for deletion
-        var i, hand;
-        while (this.removeHandlers.length > 0) {
-            hand = this.removeHandlers.pop();
-            i = this.handlers.indexOf(hand);
-            if (i >= 0) {
-                this.handlers.splice(i, 1);
-            }
-        }
-
-        // add handlers scheduled for addition
-        while (this.addHandlers.length > 0) {
-            this.handlers.push(this.addHandlers.pop());
-        }
-
-        // handle graceful disconnect
-        if (this.disconnecting && this._requests.length === 0) {
-            this.deleteTimedHandler(this._disconnectTimeout);
-            this._disconnectTimeout = null;
-            this._doDisconnect();
-            return;
-        }
-
-        var typ = elem.getAttribute("type");
-        var cond, conflict;
-        if (typ !== null && typ == "terminate") {
-            // Don't process stanzas that come in after disconnect
-            if (this.disconnecting) {
-                return;
-            }
-
-            // an error occurred
-            cond = elem.getAttribute("condition");
-            conflict = elem.getElementsByTagName("conflict");
-            if (cond !== null) {
-                if (cond == "remote-stream-error" && conflict.length > 0) {
-                    cond = "conflict";
-                }
-                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
-            } else {
-                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
-            }
-            this.disconnect();
-            return;
-        }
-
-        // send each incoming stanza through the handler chain
-        var that = this;
-        Strophe.forEachChild(elem, null, function (child) {
-            var i, newList;
-            // process handlers
-            newList = that.handlers;
-            that.handlers = [];
-            for (i = 0; i < newList.length; i++) {
-                var hand = newList[i];
-                if (hand.isMatch(child) &&
-                    (that.authenticated || !hand.user)) {
-                    if (hand.run(child)) {
-                        that.handlers.push(hand);
-                    }
-                } else {
-                    that.handlers.push(hand);
-                }
-            }
-        });
-    },
-
-    /** PrivateFunction: _sendTerminate
-     *  _Private_ function to send initial disconnect sequence.
-     *
-     *  This is the first step in a graceful disconnect.  It sends
-     *  the BOSH server a terminate body and includes an unavailable
-     *  presence if authentication has completed.
-     */
-    _sendTerminate: function ()
-    {
-        Strophe.info("_sendTerminate was called");
-        var body = this._buildBody().attrs({type: "terminate"});
-
-        if (this.authenticated) {
-            body.c('presence', {
-                xmlns: Strophe.NS.CLIENT,
-                type: 'unavailable'
-            });
-        }
-
-        this.disconnecting = true;
-
-        var req = new Strophe.Request(body.tree(),
-                                      this._onRequestStateChange.bind(
-                                          this, this._dataRecv.bind(this)),
-                                      body.tree().getAttribute("rid"));
-
-        this._requests.push(req);
-        this._throttledRequestHandler();
-    },
-
-    /** PrivateFunction: _connect_cb
-     *  _Private_ handler for initial connection request.
-     *
-     *  This handler is used to process the initial connection request
-     *  response from the BOSH server. It is used to set up authentication
-     *  handlers and start the authentication process.
-     *
-     *  SASL authentication will be attempted if available, otherwise
-     *  the code will fall back to legacy authentication.
-     *
-     *  Parameters:
-     *    (Strophe.Request) req - The current request.
-     */
-    _connect_cb: function (req)
-    {
-        Strophe.info("_connect_cb was called");
-
-        this.connected = true;
-        var bodyWrap = req.getResponse();
-        if (!bodyWrap) { return; }
-
-        this.xmlInput(bodyWrap);
-        this.rawInput(Strophe.serialize(bodyWrap));
-
-        var typ = bodyWrap.getAttribute("type");
-        var cond, conflict;
-        if (typ !== null && typ == "terminate") {
-            // an error occurred
-            cond = bodyWrap.getAttribute("condition");
-            conflict = bodyWrap.getElementsByTagName("conflict");
-            if (cond !== null) {
-                if (cond == "remote-stream-error" && conflict.length > 0) {
-                    cond = "conflict";
-                }
-                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
-            } else {
-                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
-            }
-            return;
-        }
-
-        // check to make sure we don't overwrite these if _connect_cb is
-        // called multiple times in the case of missing stream:features
-        if (!this.sid) {
-            this.sid = bodyWrap.getAttribute("sid");
-        }
-        if (!this.stream_id) {
-            this.stream_id = bodyWrap.getAttribute("authid");
-        }
-        var wind = bodyWrap.getAttribute('requests');
-        if (wind) { this.window = parseInt(wind, 10); }
-        var hold = bodyWrap.getAttribute('hold');
-        if (hold) { this.hold = parseInt(hold, 10); }
-        var wait = bodyWrap.getAttribute('wait');
-        if (wait) { this.wait = parseInt(wait, 10); }
-
-
-        var do_sasl_plain = false;
-        var do_sasl_digest_md5 = false;
-        var do_sasl_anonymous = false;
-
-        var mechanisms = bodyWrap.getElementsByTagName("mechanism");
-        var i, mech, auth_str, hashed_auth_str;
-        if (mechanisms.length > 0) {
-            for (i = 0; i < mechanisms.length; i++) {
-                mech = Strophe.getText(mechanisms[i]);
-                if (mech == 'DIGEST-MD5') {
-                    do_sasl_digest_md5 = true;
-                } else if (mech == 'PLAIN') {
-                    do_sasl_plain = true;
-                } else if (mech == 'ANONYMOUS') {
-                    do_sasl_anonymous = true;
-                }
-            }
-        } else {
-            // we didn't get stream:features yet, so we need wait for it
-            // by sending a blank poll request
-            var body = this._buildBody();
-            this._requests.push(
-                new Strophe.Request(body.tree(),
-                                    this._onRequestStateChange.bind(
-                                        this, this._connect_cb.bind(this)),
-                                    body.tree().getAttribute("rid")));
-            this._throttledRequestHandler();
-            return;
-        }
-
-        if (Strophe.getNodeFromJid(this.jid) === null &&
-            do_sasl_anonymous) {
-            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
-            this._sasl_success_handler = this._addSysHandler(
-                this._sasl_success_cb.bind(this), null,
-                "success", null, null);
-            this._sasl_failure_handler = this._addSysHandler(
-                this._sasl_failure_cb.bind(this), null,
-                "failure", null, null);
-
-            this.send($build("auth", {
-                xmlns: Strophe.NS.SASL,
-                mechanism: "ANONYMOUS"
-            }).tree());
-        } else if (Strophe.getNodeFromJid(this.jid) === null) {
-            // we don't have a node, which is required for non-anonymous
-            // client connections
-            this._changeConnectStatus(Strophe.Status.CONNFAIL,
-                                      'x-strophe-bad-non-anon-jid');
-            this.disconnect();
-        } else if (do_sasl_digest_md5) {
-            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
-            this._sasl_challenge_handler = this._addSysHandler(
-                this._sasl_challenge1_cb.bind(this), null,
-                "challenge", null, null);
-            this._sasl_failure_handler = this._addSysHandler(
-                this._sasl_failure_cb.bind(this), null,
-                "failure", null, null);
-
-            this.send($build("auth", {
-                xmlns: Strophe.NS.SASL,
-                mechanism: "DIGEST-MD5"
-            }).tree());
-        } else if (do_sasl_plain) {
-            // Build the plain auth string (barejid null
-            // username null password) and base 64 encoded.
-            auth_str = Strophe.getBareJidFromJid(this.jid);
-            auth_str = auth_str + "\u0000";
-            auth_str = auth_str + Strophe.getNodeFromJid(this.jid);
-            auth_str = auth_str + "\u0000";
-            auth_str = auth_str + this.pass;
-
-            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
-            this._sasl_success_handler = this._addSysHandler(
-                this._sasl_success_cb.bind(this), null,
-                "success", null, null);
-            this._sasl_failure_handler = this._addSysHandler(
-                this._sasl_failure_cb.bind(this), null,
-                "failure", null, null);
-
-            hashed_auth_str = Base64.encode(auth_str);
-            this.send($build("auth", {
-                xmlns: Strophe.NS.SASL,
-                mechanism: "PLAIN"
-            }).t(hashed_auth_str).tree());
-        } else {
-            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
-            this._addSysHandler(this._auth1_cb.bind(this), null, null,
-                                null, "_auth_1");
-
-            this.send($iq({
-                type: "get",
-                to: this.domain,
-                id: "_auth_1"
-            }).c("query", {
-                xmlns: Strophe.NS.AUTH
-            }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
-        }
-    },
-
-    /** PrivateFunction: _sasl_challenge1_cb
-     *  _Private_ handler for DIGEST-MD5 SASL authentication.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The challenge stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_challenge1_cb: function (elem)
-    {
-        var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
-
-        var challenge = Base64.decode(Strophe.getText(elem));
-        var cnonce = MD5.hexdigest(Math.random() * 1234567890);
-        var realm = "";
-        var host = null;
-        var nonce = "";
-        var qop = "";
-        var matches;
-
-        // remove unneeded handlers
-        this.deleteHandler(this._sasl_failure_handler);
-
-        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/" + this.domain;
-        if (host !== null) {
-            digest_uri = digest_uri + "/" + host;
-        }
-
-        var A1 = MD5.hash(Strophe.getNodeFromJid(this.jid) +
-                          ":" + realm + ":" + this.pass) +
-            ":" + nonce + ":" + cnonce;
-        var A2 = 'AUTHENTICATE:' + digest_uri;
-
-        var responseText = "";
-        responseText += 'username=' +
-            this._quote(Strophe.getNodeFromJid(this.jid)) + ',';
-        responseText += 'realm=' + this._quote(realm) + ',';
-        responseText += 'nonce=' + this._quote(nonce) + ',';
-        responseText += 'cnonce=' + this._quote(cnonce) + ',';
-        responseText += 'nc="00000001",';
-        responseText += 'qop="auth",';
-        responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
-        responseText += 'response=' + this._quote(
-            MD5.hexdigest(MD5.hexdigest(A1) + ":" +
-                          nonce + ":00000001:" +
-                          cnonce + ":auth:" +
-                          MD5.hexdigest(A2))) + ',';
-        responseText += 'charset="utf-8"';
-
-        this._sasl_challenge_handler = this._addSysHandler(
-            this._sasl_challenge2_cb.bind(this), null,
-            "challenge", null, null);
-        this._sasl_success_handler = this._addSysHandler(
-            this._sasl_success_cb.bind(this), null,
-            "success", null, null);
-        this._sasl_failure_handler = this._addSysHandler(
-            this._sasl_failure_cb.bind(this), null,
-            "failure", null, null);
-
-        this.send($build('response', {
-            xmlns: Strophe.NS.SASL
-        }).t(Base64.encode(responseText)).tree());
-
-        return false;
-    },
-
-    /** PrivateFunction: _quote
-     *  _Private_ utility function to backslash escape and quote strings.
-     *
-     *  Parameters:
-     *    (String) str - The string to be quoted.
-     *
-     *  Returns:
-     *    quoted string
-     */
-    _quote: function (str)
-    {
-        return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
-        //" end string workaround for emacs
-    },
-
-
-    /** PrivateFunction: _sasl_challenge2_cb
-     *  _Private_ handler for second step of DIGEST-MD5 SASL authentication.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The challenge stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_challenge2_cb: function (elem)
-    {
-        // remove unneeded handlers
-        this.deleteHandler(this._sasl_success_handler);
-        this.deleteHandler(this._sasl_failure_handler);
-
-        this._sasl_success_handler = this._addSysHandler(
-            this._sasl_success_cb.bind(this), null,
-            "success", null, null);
-        this._sasl_failure_handler = this._addSysHandler(
-            this._sasl_failure_cb.bind(this), null,
-            "failure", null, null);
-        this.send($build('response', {xmlns: Strophe.NS.SASL}).tree());
-        return false;
-    },
-
-    /** PrivateFunction: _auth1_cb
-     *  _Private_ handler for legacy authentication.
-     *
-     *  This handler is called in response to the initial <iq type='get'/>
-     *  for legacy authentication.  It builds an authentication <iq/> and
-     *  sends it, creating a handler (calling back to _auth2_cb()) to
-     *  handle the result
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The stanza that triggered the callback.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _auth1_cb: function (elem)
-    {
-        // build plaintext auth iq
-        var iq = $iq({type: "set", id: "_auth_2"})
-            .c('query', {xmlns: Strophe.NS.AUTH})
-            .c('username', {}).t(Strophe.getNodeFromJid(this.jid))
-            .up()
-            .c('password').t(this.pass);
-
-        if (!Strophe.getResourceFromJid(this.jid)) {
-            // since the user has not supplied a resource, we pick
-            // a default one here.  unlike other auth methods, the server
-            // cannot do this for us.
-            this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';
-        }
-        iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
-
-        this._addSysHandler(this._auth2_cb.bind(this), null,
-                            null, null, "_auth_2");
-
-        this.send(iq.tree());
-
-        return false;
-    },
-
-    /** PrivateFunction: _sasl_success_cb
-     *  _Private_ handler for succesful SASL authentication.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The matching stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_success_cb: function (elem)
-    {
-        Strophe.info("SASL authentication succeeded.");
-
-        // remove old handlers
-        this.deleteHandler(this._sasl_failure_handler);
-        this._sasl_failure_handler = null;
-        if (this._sasl_challenge_handler) {
-            this.deleteHandler(this._sasl_challenge_handler);
-            this._sasl_challenge_handler = null;
-        }
-
-        this._addSysHandler(this._sasl_auth1_cb.bind(this), null,
-                            "stream:features", null, null);
-
-        // we must send an xmpp:restart now
-        this._sendRestart();
-
-        return false;
-    },
-
-    /** PrivateFunction: _sasl_auth1_cb
-     *  _Private_ handler to start stream binding.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The matching stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_auth1_cb: function (elem)
-    {
-        // save stream:features for future usage
-        this.features = elem;
-
-        var i, child;
-
-        for (i = 0; i < elem._childNodes.length; i++) {
-            child = elem._childNodes[i];
-            if (child.nodeName.toLowerCase() == 'bind') {
-                this.do_bind = true;
-            }
-
-            if (child.nodeName.toLowerCase() == 'session') {
-                this.do_session = true;
-            }
-        }
-
-        if (!this.do_bind) {
-            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-            return false;
-        } else {
-            this._addSysHandler(this._sasl_bind_cb.bind(this), null, null,
-                                null, "_bind_auth_2");
-
-            var resource = Strophe.getResourceFromJid(this.jid);
-            if (resource) {
-                this.send($iq({type: "set", id: "_bind_auth_2"})
-                          .c('bind', {xmlns: Strophe.NS.BIND})
-                          .c('resource', {}).t(resource).tree());
-            } else {
-                this.send($iq({type: "set", id: "_bind_auth_2"})
-                          .c('bind', {xmlns: Strophe.NS.BIND})
-                          .tree());
-            }
-        }
-
-        return false;
-    },
-
-    /** PrivateFunction: _sasl_bind_cb
-     *  _Private_ handler for binding result and session start.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The matching stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_bind_cb: function (elem)
-    {
-        if (elem.getAttribute("type") == "error") {
-            Strophe.info("SASL binding failed.");
-            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-            return false;
-        }
-
-        // TODO - need to grab errors
-        var bind = elem.getElementsByTagName("bind");
-        var jidNode;
-        if (bind.length > 0) {
-            // Grab jid
-            jidNode = bind[0].getElementsByTagName("jid");
-            if (jidNode.length > 0) {
-                this.jid = Strophe.getText(jidNode[0]);
-
-                if (this.do_session) {
-                    this._addSysHandler(this._sasl_session_cb.bind(this),
-                                        null, null, null, "_session_auth_2");
-
-                    this.send($iq({type: "set", id: "_session_auth_2"})
-                                  .c('session', {xmlns: Strophe.NS.SESSION})
-                                  .tree());
-                } else {
-                    this.authenticated = true;
-                    this._changeConnectStatus(Strophe.Status.CONNECTED, null);
-                }
-            }
-        } else {
-            Strophe.info("SASL binding failed.");
-            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-            return false;
-        }
-    },
-
-    /** PrivateFunction: _sasl_session_cb
-     *  _Private_ handler to finish successful SASL connection.
-     *
-     *  This sets Connection.authenticated to true on success, which
-     *  starts the processing of user handlers.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The matching stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_session_cb: function (elem)
-    {
-        if (elem.getAttribute("type") == "result") {
-            this.authenticated = true;
-            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
-        } else if (elem.getAttribute("type") == "error") {
-            Strophe.info("Session creation failed.");
-            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-            return false;
-        }
-
-        return false;
-    },
-
-    /** PrivateFunction: _sasl_failure_cb
-     *  _Private_ handler for SASL authentication failure.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The matching stanza.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _sasl_failure_cb: function (elem)
-    {
-        // delete unneeded handlers
-        if (this._sasl_success_handler) {
-            this.deleteHandler(this._sasl_success_handler);
-            this._sasl_success_handler = null;
-        }
-        if (this._sasl_challenge_handler) {
-            this.deleteHandler(this._sasl_challenge_handler);
-            this._sasl_challenge_handler = null;
-        }
-
-        this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-        return false;
-    },
-
-    /** PrivateFunction: _auth2_cb
-     *  _Private_ handler to finish legacy authentication.
-     *
-     *  This handler is called when the result from the jabber:iq:auth
-     *  <iq/> stanza is returned.
-     *
-     *  Parameters:
-     *    (XMLElement) elem - The stanza that triggered the callback.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _auth2_cb: function (elem)
-    {
-        if (elem.getAttribute("type") == "result") {
-            this.authenticated = true;
-            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
-        } else if (elem.getAttribute("type") == "error") {
-            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
-            this.disconnect();
-        }
-
-        return false;
-    },
-
-    /** PrivateFunction: _addSysTimedHandler
-     *  _Private_ function to add a system level timed handler.
-     *
-     *  This function is used to add a Strophe.TimedHandler for the
-     *  library code.  System timed handlers are allowed to run before
-     *  authentication is complete.
-     *
-     *  Parameters:
-     *    (Integer) period - The period of the handler.
-     *    (Function) handler - The callback function.
-     */
-    _addSysTimedHandler: function (period, handler)
-    {
-        var thand = new Strophe.TimedHandler(period, handler);
-        thand.user = false;
-        this.addTimeds.push(thand);
-        return thand;
-    },
-
-    /** PrivateFunction: _addSysHandler
-     *  _Private_ function to add a system level stanza handler.
-     *
-     *  This function is used to add a Strophe.Handler for the
-     *  library code.  System stanza handlers are allowed to run before
-     *  authentication is complete.
-     *
-     *  Parameters:
-     *    (Function) handler - The callback function.
-     *    (String) ns - The namespace to match.
-     *    (String) name - The stanza name to match.
-     *    (String) type - The stanza type attribute to match.
-     *    (String) id - The stanza id attribute to match.
-     */
-    _addSysHandler: function (handler, ns, name, type, id)
-    {
-        var hand = new Strophe.Handler(handler, ns, name, type, id);
-        hand.user = false;
-        this.addHandlers.push(hand);
-        return hand;
-    },
-
-    /** PrivateFunction: _onDisconnectTimeout
-     *  _Private_ timeout handler for handling non-graceful disconnection.
-     *
-     *  If the graceful disconnect process does not complete within the
-     *  time allotted, this handler finishes the disconnect anyway.
-     *
-     *  Returns:
-     *    false to remove the handler.
-     */
-    _onDisconnectTimeout: function ()
-    {
-        Strophe.info("_onDisconnectTimeout was called");
-
-        // cancel all remaining requests and clear the queue
-        var req;
-        while (this._requests.length > 0) {
-            req = this._requests.pop();
-            req.abort = true;
-            req.xhr.abort();
-            // jslint complains, but this is fine. setting to empty func
-            // is necessary for IE6
-            req.xhr.onreadystatechange = function () {};
-        }
-
-        // actually disconnect
-        this._doDisconnect();
-
-        return false;
-    },
-
-    /** PrivateFunction: _onIdle
-     *  _Private_ handler to process events during idle cycle.
-     *
-     *  This handler is called every 100ms to fire timed handlers that
-     *  are ready and keep poll requests going.
-     */
-    _onIdle: function ()
-    {
-        var i, thand, since, newList;
-
-        // add timed handlers scheduled for addition
-        // NOTE: we add before remove in the case a timed handler is
-        // added and then deleted before the next _onIdle() call.
-        while (this.addTimeds.length > 0) {
-            this.timedHandlers.push(this.addTimeds.pop());
-        }
-
-        // remove timed handlers that have been scheduled for deletion
-        while (this.removeTimeds.length > 0) {
-            thand = this.removeTimeds.pop();
-            i = this.timedHandlers.indexOf(thand);
-            if (i >= 0) {
-                this.timedHandlers.splice(i, 1);
-            }
-        }
-
-        // call ready timed handlers
-        var now = new Date().getTime();
-        newList = [];
-        for (i = 0; i < this.timedHandlers.length; i++) {
-            thand = this.timedHandlers[i];
-            if (this.authenticated || !thand.user) {
-                since = thand.lastCalled + thand.period;
-                if (since - now <= 0) {
-                    if (thand.run()) {
-                        newList.push(thand);
-                    }
-                } else {
-                    newList.push(thand);
-                }
-            }
-        }
-        this.timedHandlers = newList;
-
-        var body, time_elapsed;
-
-        // if no requests are in progress, poll
-        if (this.authenticated && this._requests.length === 0 &&
-            this._data.length === 0 && !this.disconnecting) {
-            Strophe.info("no requests during idle cycle, sending " +
-                         "blank request");
-            this._data.push(null);
-        }
-
-        if (this._requests.length < 2 && this._data.length > 0 &&
-            !this.paused) {
-            body = this._buildBody();
-            for (i = 0; i < this._data.length; i++) {
-                if (this._data[i] !== null) {
-                    if (this._data[i] === "restart") {
-                        body.attrs({
-                            to: this.domain,
-                            "xml:lang": "en",
-                            "xmpp:restart": "true",
-                            "xmlns:xmpp": Strophe.NS.BOSH
-                        });
-                    } else {
-                        body.cnode(this._data[i]).up();
-                    }
-                }
-            }
-            delete this._data;
-            this._data = [];
-            this._requests.push(
-                new Strophe.Request(body.tree(),
-                                    this._onRequestStateChange.bind(
-                                        this, this._dataRecv.bind(this)),
-                                    body.tree().getAttribute("rid")));
-            this._processRequest(this._requests.length - 1);
-        }
-
-        if (this._requests.length > 0) {
-            time_elapsed = this._requests[0].age();
-            if (this._requests[0].dead !== null) {
-                if (this._requests[0].timeDead() >
-                    Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
-                    this._throttledRequestHandler();
-                }
-            }
-
-            if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
-                Strophe.warn("Request " +
-                             this._requests[0].id +
-                             " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) +
-                             " seconds since last activity");
-                this._throttledRequestHandler();
-            }
-        }
-
-        // reactivate the timer
-        clearTimeout(this._idleTimeout);
-        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
-    }
-};
-
-if (callback) {
-    callback(Strophe, $build, $msg, $iq, $pres);
-}
-
-})(function () {
-    window.Strophe = arguments[0];
-    window.$build = arguments[1];
-    window.$msg = arguments[2];
-    window.$iq = arguments[3];
-    window.$pres = arguments[4];
-});
diff --git a/contrib/jitsimeetbridge/unjingle/unjingle.js b/contrib/jitsimeetbridge/unjingle/unjingle.js
deleted file mode 100644
index 3dfe759914..0000000000
--- a/contrib/jitsimeetbridge/unjingle/unjingle.js
+++ /dev/null
@@ -1,48 +0,0 @@
-var strophe = require("./strophe/strophe.js").Strophe;
-
-var Strophe = strophe.Strophe;
-var $iq     = strophe.$iq;
-var $msg    = strophe.$msg;
-var $build  = strophe.$build;
-var $pres   = strophe.$pres;
-
-var jsdom = require("jsdom");
-var window = jsdom.jsdom().parentWindow;
-var $ = require('jquery')(window);
-
-var stropheJingle = require("./strophe.jingle.sdp.js");
-
-
-var input = '';
-
-process.stdin.on('readable', function() {
-  var chunk = process.stdin.read();
-  if (chunk !== null) {
-    input += chunk;
-  }
-});
-
-process.stdin.on('end', function() {
-	if (process.argv[2] == '--jingle') {
-		var elem = $(input);
-		// app does:
-		// sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
-		//console.log(elem.find('>content'));
-		var sdp = new stropheJingle.SDP('');
-		sdp.fromJingle(elem);
-		console.log(sdp.raw);
-	} else if (process.argv[2] == '--sdp') {
-		var sdp = new stropheJingle.SDP(input);
-		var accept = $iq({to: '%(tojid)s',
-			type: 'set'})
-			.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
-			    //action: 'session-accept',
-			    action: '%(action)s',
-			    initiator: '%(initiator)s',
-			    responder: '%(responder)s',
-			    sid: '%(sid)s' });
-		sdp.toJingle(accept, 'responder');
-		console.log(Strophe.serialize(accept));
-	}
-});
-
diff --git a/debian/changelog b/debian/changelog
index b6a51d6903..b1e61e7c8a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+matrix-synapse-py3 (1.60.0~rc2+nmu1) UNRELEASED; urgency=medium
+
+  * Non-maintainer upload.
+  * Remove unused `jitsimeetbridge` experiment from `contrib` directory.
+
+ -- Synapse Packaging team <packages@matrix.org>  Sun, 29 May 2022 14:44:45 +0100
+
 matrix-synapse-py3 (1.60.0~rc2) stable; urgency=medium
 
   * New Synapse release 1.60.0rc2.
diff --git a/debian/copyright b/debian/copyright
index 95c21ea12a..902b18fa41 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -22,29 +22,6 @@ Files: synapse/config/repository.py
 Copyright: 2014-2015, matrix.org
 License: Apache-2.0
 
-Files: contrib/jitsimeetbridge/unjingle/strophe/base64.js
-Copyright: Public Domain (Tyler Akins http://rumkin.com)
-License: public-domain
- 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
-
-Files: contrib/jitsimeetbridge/unjingle/strophe/md5.js
-Copyright: 1999-2002, Paul Johnston & Contributors
-License: BSD-3-clause
-
-Files: contrib/jitsimeetbridge/unjingle/strophe/strophe.js
-Copyright: 2006-2008, OGG, LLC
-License: Expat
-
-Files: contrib/jitsimeetbridge/unjingle/strophe/XMLHttpRequest.js
-Copyright: 2010 passive.ly LLC
-License: Expat
-
-Files: contrib/jitsimeetbridge/unjingle/*.js
-Copyright: 2014 Jitsi
-License: Apache-2.0
-
 Files: debian/*
 Copyright: 2016-2017, Erik Johnston <erik@matrix.org>
 	   2017, Rahul De <rahulde@swecha.net>