forked from MirrorHub/synapse
Remove contrib/jitsimeetbridge (#12909)
This commit is contained in:
parent
72df42078b
commit
563ef172ae
12 changed files with 8 additions and 5549 deletions
1
changelog.d/12909.removal
Normal file
1
changelog.d/12909.removal
Normal file
|
@ -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
|
|
||||||
--- 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];
|
|
|
@ -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;
|
|
File diff suppressed because it is too large
Load diff
|
@ -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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
7
debian/changelog
vendored
7
debian/changelog
vendored
|
@ -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.
|
||||||
|
|
23
debian/copyright
vendored
23
debian/copyright
vendored
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue