@@ -0,0 +1 @@ | |||||
Remove `contrib/jitsimeetbridge`. This was an unused experiment that hasn't been meaningfully changed since 2014. |
@@ -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)]) |
@@ -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 | |||||
+++ 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 | |||||
+++ 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]; |
@@ -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; | |||||
}; | |||||
@@ -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; | |||||
@@ -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(); | |||||
} | |||||
}; |
@@ -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; |
@@ -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; |
@@ -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)); | |||||
} | |||||
}); | |||||
@@ -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 | matrix-synapse-py3 (1.60.0~rc2) stable; urgency=medium | ||||
* New Synapse release 1.60.0rc2. | * New Synapse release 1.60.0rc2. | ||||
@@ -22,29 +22,6 @@ Files: synapse/config/repository.py | |||||
Copyright: 2014-2015, matrix.org | Copyright: 2014-2015, matrix.org | ||||
License: Apache-2.0 | 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/* | Files: debian/* | ||||
Copyright: 2016-2017, Erik Johnston <erik@matrix.org> | Copyright: 2016-2017, Erik Johnston <erik@matrix.org> | ||||
2017, Rahul De <rahulde@swecha.net> | 2017, Rahul De <rahulde@swecha.net> | ||||