You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

strophejingle.bundle.js 142KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333
  1. /*! strophe.js v1.1.3 - built on 20-01-2014 */
  2. function b64_sha1(a){return binb2b64(core_sha1(str2binb(a),8*a.length))}function str_sha1(a){return binb2str(core_sha1(str2binb(a),8*a.length))}function b64_hmac_sha1(a,b){return binb2b64(core_hmac_sha1(a,b))}function str_hmac_sha1(a,b){return binb2str(core_hmac_sha1(a,b))}function core_sha1(a,b){a[b>>5]|=128<<24-b%32,a[(b+64>>9<<4)+15]=b;var c,d,e,f,g,h,i,j,k=new Array(80),l=1732584193,m=-271733879,n=-1732584194,o=271733878,p=-1009589776;for(c=0;c<a.length;c+=16){for(f=l,g=m,h=n,i=o,j=p,d=0;80>d;d++)k[d]=16>d?a[c+d]:rol(k[d-3]^k[d-8]^k[d-14]^k[d-16],1),e=safe_add(safe_add(rol(l,5),sha1_ft(d,m,n,o)),safe_add(safe_add(p,k[d]),sha1_kt(d))),p=o,o=n,n=rol(m,30),m=l,l=e;l=safe_add(l,f),m=safe_add(m,g),n=safe_add(n,h),o=safe_add(o,i),p=safe_add(p,j)}return[l,m,n,o,p]}function sha1_ft(a,b,c,d){return 20>a?b&c|~b&d:40>a?b^c^d:60>a?b&c|b&d|c&d:b^c^d}function sha1_kt(a){return 20>a?1518500249:40>a?1859775393:60>a?-1894007588:-899497514}function core_hmac_sha1(a,b){var c=str2binb(a);c.length>16&&(c=core_sha1(c,8*a.length));for(var d=new Array(16),e=new Array(16),f=0;16>f;f++)d[f]=909522486^c[f],e[f]=1549556828^c[f];var g=core_sha1(d.concat(str2binb(b)),512+8*b.length);return core_sha1(e.concat(g),672)}function safe_add(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c}function rol(a,b){return a<<b|a>>>32-b}function str2binb(a){for(var b=[],c=255,d=0;d<8*a.length;d+=8)b[d>>5]|=(a.charCodeAt(d/8)&c)<<24-d%32;return b}function binb2str(a){for(var b="",c=255,d=0;d<32*a.length;d+=8)b+=String.fromCharCode(a[d>>5]>>>24-d%32&c);return b}function binb2b64(a){for(var b,c,d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",e="",f=0;f<4*a.length;f+=3)for(b=(a[f>>2]>>8*(3-f%4)&255)<<16|(a[f+1>>2]>>8*(3-(f+1)%4)&255)<<8|a[f+2>>2]>>8*(3-(f+2)%4)&255,c=0;4>c;c++)e+=8*f+6*c>32*a.length?"=":d.charAt(b>>6*(3-c)&63);return e}var Base64=function(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",b={encode:function(b){var c,d,e,f,g,h,i,j="",k=0;do c=b.charCodeAt(k++),d=b.charCodeAt(k++),e=b.charCodeAt(k++),f=c>>2,g=(3&c)<<4|d>>4,h=(15&d)<<2|e>>6,i=63&e,isNaN(d)?h=i=64:isNaN(e)&&(i=64),j=j+a.charAt(f)+a.charAt(g)+a.charAt(h)+a.charAt(i);while(k<b.length);return j},decode:function(b){var c,d,e,f,g,h,i,j="",k=0;b=b.replace(/[^A-Za-z0-9\+\/\=]/g,"");do f=a.indexOf(b.charAt(k++)),g=a.indexOf(b.charAt(k++)),h=a.indexOf(b.charAt(k++)),i=a.indexOf(b.charAt(k++)),c=f<<2|g>>4,d=(15&g)<<4|h>>2,e=(3&h)<<6|i,j+=String.fromCharCode(c),64!=h&&(j+=String.fromCharCode(d)),64!=i&&(j+=String.fromCharCode(e));while(k<b.length);return j}};return b}(),MD5=function(){var a=function(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c},b=function(a,b){return a<<b|a>>>32-b},c=function(a){for(var b=[],c=0;c<8*a.length;c+=8)b[c>>5]|=(255&a.charCodeAt(c/8))<<c%32;return b},d=function(a){for(var b="",c=0;c<32*a.length;c+=8)b+=String.fromCharCode(a[c>>5]>>>c%32&255);return b},e=function(a){for(var b="0123456789abcdef",c="",d=0;d<4*a.length;d++)c+=b.charAt(a[d>>2]>>d%4*8+4&15)+b.charAt(a[d>>2]>>d%4*8&15);return c},f=function(c,d,e,f,g,h){return a(b(a(a(d,c),a(f,h)),g),e)},g=function(a,b,c,d,e,g,h){return f(b&c|~b&d,a,b,e,g,h)},h=function(a,b,c,d,e,g,h){return f(b&d|c&~d,a,b,e,g,h)},i=function(a,b,c,d,e,g,h){return f(b^c^d,a,b,e,g,h)},j=function(a,b,c,d,e,g,h){return f(c^(b|~d),a,b,e,g,h)},k=function(b,c){b[c>>5]|=128<<c%32,b[(c+64>>>9<<4)+14]=c;for(var d,e,f,k,l=1732584193,m=-271733879,n=-1732584194,o=271733878,p=0;p<b.length;p+=16)d=l,e=m,f=n,k=o,l=g(l,m,n,o,b[p+0],7,-680876936),o=g(o,l,m,n,b[p+1],12,-389564586),n=g(n,o,l,m,b[p+2],17,606105819),m=g(m,n,o,l,b[p+3],22,-1044525330),l=g(l,m,n,o,b[p+4],7,-176418897),o=g(o,l,m,n,b[p+5],12,1200080426),n=g(n,o,l,m,b[p+6],17,-1473231341),m=g(m,n,o,l,b[p+7],22,-45705983),l=g(l,m,n,o,b[p+8],7,1770035416),o=g(o,l,m,n,b[p+9],12,-1958414417),n=g(n,o,l,m,b[p+10],17,-42063),m=g(m,n,o,l,b[p+11],22,-1990404162),l=g(l,m,n,o,b[p+12],7,1804603682),o=g(o,l,m,n,b[p+13],12,-40341101),n=g(n,o,l,m,b[p+14],17,-1502002290),m=g(m,n,o,l,b[p+15],22,1236535329),l=h(l,m,n,o,b[p+1],5,-165796510),o=h(o,l,m,n,b[p+6],9,-1069501632),n=h(n,o,l,m,b[p+11],14,643717713),m=h(m,n,o,l,b[p+0],20,-373897302),l=h(l,m,n,o,b[p+5],5,-701558691),o=h(o,l,m,n,b[p+10],9,38016083),n=h(n,o,l,m,b[p+15],14,-660478335),m=h(m,n,o,l,b[p+4],20,-405537848),l=h(l,m,n,o,b[p+9],5,568446438),o=h(o,l,m,n,b[p+14],9,-1019803690),n=h(n,o,l,m,b[p+3],14,-187363961),m=h(m,n,o,l,b[p+8],20,1163531501),l=h(l,m,n,o,b[p+13],5,-1444681467),o=h(o,l,m,n,b[p+2],9,-51403784),n=h(n,o,l,m,b[p+7],14,1735328473),m=h(m,n,o,l,b[p+12],20,-1926607734),l=i(l,m,n,o,b[p+5],4,-378558),o=i(o,l,m,n,b[p+8],11,-2022574463),n=i(n,o,l,m,b[p+11],16,1839030562),m=i(m,n,o,l,b[p+14],23,-35309556),l=i(l,m,n,o,b[p+1],4,-1530992060),o=i(o,l,m,n,b[p+4],11,1272893353),n=i(n,o,l,m,b[p+7],16,-155497632),m=i(m,n,o,l,b[p+10],23,-1094730640),l=i(l,m,n,o,b[p+13],4,681279174),o=i(o,l,m,n,b[p+0],11,-358537222),n=i(n,o,l,m,b[p+3],16,-722521979),m=i(m,n,o,l,b[p+6],23,76029189),l=i(l,m,n,o,b[p+9],4,-640364487),o=i(o,l,m,n,b[p+12],11,-421815835),n=i(n,o,l,m,b[p+15],16,530742520),m=i(m,n,o,l,b[p+2],23,-995338651),l=j(l,m,n,o,b[p+0],6,-198630844),o=j(o,l,m,n,b[p+7],10,1126891415),n=j(n,o,l,m,b[p+14],15,-1416354905),m=j(m,n,o,l,b[p+5],21,-57434055),l=j(l,m,n,o,b[p+12],6,1700485571),o=j(o,l,m,n,b[p+3],10,-1894986606),n=j(n,o,l,m,b[p+10],15,-1051523),m=j(m,n,o,l,b[p+1],21,-2054922799),l=j(l,m,n,o,b[p+8],6,1873313359),o=j(o,l,m,n,b[p+15],10,-30611744),n=j(n,o,l,m,b[p+6],15,-1560198380),m=j(m,n,o,l,b[p+13],21,1309151649),l=j(l,m,n,o,b[p+4],6,-145523070),o=j(o,l,m,n,b[p+11],10,-1120210379),n=j(n,o,l,m,b[p+2],15,718787259),m=j(m,n,o,l,b[p+9],21,-343485551),l=a(l,d),m=a(m,e),n=a(n,f),o=a(o,k);return[l,m,n,o]},l={hexdigest:function(a){return e(k(c(a),8*a.length))},hash:function(a){return d(k(c(a),8*a.length))}};return l}();Function.prototype.bind||(Function.prototype.bind=function(a){var b=this,c=Array.prototype.slice,d=Array.prototype.concat,e=c.call(arguments,1);return function(){return b.apply(a?a:this,d.call(e,c.call(arguments,0)))}}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){var b=this.length,c=Number(arguments[1])||0;for(c=0>c?Math.ceil(c):Math.floor(c),0>c&&(c+=b);b>c;c++)if(c in this&&this[c]===a)return c;return-1}),function(a){function b(a,b){return new f.Builder(a,b)}function c(a){return new f.Builder("message",a)}function d(a){return new f.Builder("iq",a)}function e(a){return new f.Builder("presence",a)}var f;f={VERSION:"1.1.3",NS:{HTTPBIND:"http://jabber.org/protocol/httpbind",BOSH:"urn:xmpp:xbosh",CLIENT:"jabber:client",AUTH:"jabber:iq:auth",ROSTER:"jabber:iq:roster",PROFILE:"jabber:iq:profile",DISCO_INFO:"http://jabber.org/protocol/disco#info",DISCO_ITEMS:"http://jabber.org/protocol/disco#items",MUC:"http://jabber.org/protocol/muc",SASL:"urn:ietf:params:xml:ns:xmpp-sasl",STREAM:"http://etherx.jabber.org/streams",BIND:"urn:ietf:params:xml:ns:xmpp-bind",SESSION:"urn:ietf:params:xml:ns:xmpp-session",VERSION:"jabber:iq:version",STANZAS:"urn:ietf:params:xml:ns:xmpp-stanzas",XHTML_IM:"http://jabber.org/protocol/xhtml-im",XHTML:"http://www.w3.org/1999/xhtml"},XHTML:{tags:["a","blockquote","br","cite","em","img","li","ol","p","span","strong","ul","body"],attributes:{a:["href"],blockquote:["style"],br:[],cite:["style"],em:[],img:["src","alt","style","height","width"],li:["style"],ol:["style"],p:["style"],span:["style"],strong:[],ul:["style"],body:[]},css:["background-color","color","font-family","font-size","font-style","font-weight","margin-left","margin-right","text-align","text-decoration"],validTag:function(a){for(var b=0;b<f.XHTML.tags.length;b++)if(a==f.XHTML.tags[b])return!0;return!1},validAttribute:function(a,b){if("undefined"!=typeof f.XHTML.attributes[a]&&f.XHTML.attributes[a].length>0)for(var c=0;c<f.XHTML.attributes[a].length;c++)if(b==f.XHTML.attributes[a][c])return!0;return!1},validCSS:function(a){for(var b=0;b<f.XHTML.css.length;b++)if(a==f.XHTML.css[b])return!0;return!1}},Status:{ERROR:0,CONNECTING:1,CONNFAIL:2,AUTHENTICATING:3,AUTHFAIL:4,CONNECTED:5,DISCONNECTED:6,DISCONNECTING:7,ATTACHED:8},LogLevel:{DEBUG:0,INFO:1,WARN:2,ERROR:3,FATAL:4},ElementType:{NORMAL:1,TEXT:3,CDATA:4,FRAGMENT:11},TIMEOUT:1.1,SECONDARY_TIMEOUT:.1,addNamespace:function(a,b){f.NS[a]=b},forEachChild:function(a,b,c){var d,e;for(d=0;d<a.childNodes.length;d++)e=a.childNodes[d],e.nodeType!=f.ElementType.NORMAL||b&&!this.isTagEqual(e,b)||c(e)},isTagEqual:function(a,b){return a.tagName.toLowerCase()==b.toLowerCase()},_xmlGenerator:null,_makeGenerator:function(){var a;return void 0===document.implementation.createDocument||document.implementation.createDocument&&document.documentMode&&document.documentMode<10?(a=this._getIEXmlDom(),a.appendChild(a.createElement("strophe"))):a=document.implementation.createDocument("jabber:client","strophe",null),a},xmlGenerator:function(){return f._xmlGenerator||(f._xmlGenerator=f._makeGenerator()),f._xmlGenerator},_getIEXmlDom:function(){for(var a=null,b=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XMLDOM"],c=0;c<b.length&&null===a;c++)try{a=new ActiveXObject(b[c])}catch(d){a=null}return a},xmlElement:function(a){if(!a)return null;var b,c,d,e=f.xmlGenerator().createElement(a);for(b=1;b<arguments.length;b++)if(arguments[b])if("string"==typeof arguments[b]||"number"==typeof arguments[b])e.appendChild(f.xmlTextNode(arguments[b]));else if("object"==typeof arguments[b]&&"function"==typeof arguments[b].sort)for(c=0;c<arguments[b].length;c++)"object"==typeof arguments[b][c]&&"function"==typeof arguments[b][c].sort&&e.setAttribute(arguments[b][c][0],arguments[b][c][1]);else if("object"==typeof arguments[b])for(d in arguments[b])arguments[b].hasOwnProperty(d)&&e.setAttribute(d,arguments[b][d]);return e},xmlescape:function(a){return a=a.replace(/\&/g,"&amp;"),a=a.replace(/</g,"&lt;"),a=a.replace(/>/g,"&gt;"),a=a.replace(/'/g,"&apos;"),a=a.replace(/"/g,"&quot;")},xmlTextNode:function(a){return f.xmlGenerator().createTextNode(a)},xmlHtmlNode:function(a){var b;if(window.DOMParser){var c=new DOMParser;b=c.parseFromString(a,"text/xml")}else b=new ActiveXObject("Microsoft.XMLDOM"),b.async="false",b.loadXML(a);return b},getText:function(a){if(!a)return null;var b="";0===a.childNodes.length&&a.nodeType==f.ElementType.TEXT&&(b+=a.nodeValue);for(var c=0;c<a.childNodes.length;c++)a.childNodes[c].nodeType==f.ElementType.TEXT&&(b+=a.childNodes[c].nodeValue);return f.xmlescape(b)},copyElement:function(a){var b,c;if(a.nodeType==f.ElementType.NORMAL){for(c=f.xmlElement(a.tagName),b=0;b<a.attributes.length;b++)c.setAttribute(a.attributes[b].nodeName.toLowerCase(),a.attributes[b].value);for(b=0;b<a.childNodes.length;b++)c.appendChild(f.copyElement(a.childNodes[b]))}else a.nodeType==f.ElementType.TEXT&&(c=f.xmlGenerator().createTextNode(a.nodeValue));return c},createHtml:function(a){var b,c,d,e,g,h,i,j,k,l,m;if(a.nodeType==f.ElementType.NORMAL)if(e=a.nodeName.toLowerCase(),f.XHTML.validTag(e))try{for(c=f.xmlElement(e),b=0;b<f.XHTML.attributes[e].length;b++)if(g=f.XHTML.attributes[e][b],h=a.getAttribute(g),"undefined"!=typeof h&&null!==h&&""!==h&&h!==!1&&0!==h)if("style"==g&&"object"==typeof h&&"undefined"!=typeof h.cssText&&(h=h.cssText),"style"==g){for(i=[],j=h.split(";"),d=0;d<j.length;d++)k=j[d].split(":"),l=k[0].replace(/^\s*/,"").replace(/\s*$/,"").toLowerCase(),f.XHTML.validCSS(l)&&(m=k[1].replace(/^\s*/,"").replace(/\s*$/,""),i.push(l+": "+m));i.length>0&&(h=i.join("; "),c.setAttribute(g,h))}else c.setAttribute(g,h);for(b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]))}catch(n){c=f.xmlTextNode("")}else for(c=f.xmlGenerator().createDocumentFragment(),b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]));else if(a.nodeType==f.ElementType.FRAGMENT)for(c=f.xmlGenerator().createDocumentFragment(),b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]));else a.nodeType==f.ElementType.TEXT&&(c=f.xmlTextNode(a.nodeValue));return c},escapeNode:function(a){return a.replace(/^\s+|\s+$/g,"").replace(/\\/g,"\\5c").replace(/ /g,"\\20").replace(/\"/g,"\\22").replace(/\&/g,"\\26").replace(/\'/g,"\\27").replace(/\//g,"\\2f").replace(/:/g,"\\3a").replace(/</g,"\\3c").replace(/>/g,"\\3e").replace(/@/g,"\\40")},unescapeNode:function(a){return a.replace(/\\20/g," ").replace(/\\22/g,'"').replace(/\\26/g,"&").replace(/\\27/g,"'").replace(/\\2f/g,"/").replace(/\\3a/g,":").replace(/\\3c/g,"<").replace(/\\3e/g,">").replace(/\\40/g,"@").replace(/\\5c/g,"\\")},getNodeFromJid:function(a){return a.indexOf("@")<0?null:a.split("@")[0]},getDomainFromJid:function(a){var b=f.getBareJidFromJid(a);if(b.indexOf("@")<0)return b;var c=b.split("@");return c.splice(0,1),c.join("@")},getResourceFromJid:function(a){var b=a.split("/");return b.length<2?null:(b.splice(0,1),b.join("/"))},getBareJidFromJid:function(a){return a?a.split("/")[0]:null},log:function(){},debug:function(a){this.log(this.LogLevel.DEBUG,a)},info:function(a){this.log(this.LogLevel.INFO,a)},warn:function(a){this.log(this.LogLevel.WARN,a)},error:function(a){this.log(this.LogLevel.ERROR,a)},fatal:function(a){this.log(this.LogLevel.FATAL,a)},serialize:function(a){var b;if(!a)return null;"function"==typeof a.tree&&(a=a.tree());var c,d,e=a.nodeName;for(a.getAttribute("_realname")&&(e=a.getAttribute("_realname")),b="<"+e,c=0;c<a.attributes.length;c++)"_realname"!=a.attributes[c].nodeName&&(b+=" "+a.attributes[c].nodeName.toLowerCase()+"='"+a.attributes[c].value.replace(/&/g,"&amp;").replace(/\'/g,"&apos;").replace(/>/g,"&gt;").replace(/</g,"&lt;")+"'");if(a.childNodes.length>0){for(b+=">",c=0;c<a.childNodes.length;c++)switch(d=a.childNodes[c],d.nodeType){case f.ElementType.NORMAL:b+=f.serialize(d);break;case f.ElementType.TEXT:b+=f.xmlescape(d.nodeValue);break;case f.ElementType.CDATA:b+="<![CDATA["+d.nodeValue+"]]>"}b+="</"+e+">"}else b+="/>";return b},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(a,b){f._connectionPlugins[a]=b}},f.Builder=function(a,b){("presence"==a||"message"==a||"iq"==a)&&(b&&!b.xmlns?b.xmlns=f.NS.CLIENT:b||(b={xmlns:f.NS.CLIENT})),this.nodeTree=f.xmlElement(a,b),this.node=this.nodeTree},f.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return f.serialize(this.nodeTree)},up:function(){return this.node=this.node.parentNode,this},attrs:function(a){for(var b in a)a.hasOwnProperty(b)&&this.node.setAttribute(b,a[b]);return this},c:function(a,b,c){var d=f.xmlElement(a,b,c);return this.node.appendChild(d),c||(this.node=d),this},cnode:function(a){var b,c=f.xmlGenerator();try{b=void 0!==c.importNode}catch(d){b=!1}var e=b?c.importNode(a,!0):f.copyElement(a);return this.node.appendChild(e),this.node=e,this},t:function(a){var b=f.xmlTextNode(a);return this.node.appendChild(b),this},h:function(a){var b=document.createElement("body");b.innerHTML=a;for(var c=f.createHtml(b);c.childNodes.length>0;)this.node.appendChild(c.childNodes[0]);return this}},f.Handler=function(a,b,c,d,e,g,h){this.handler=a,this.ns=b,this.name=c,this.type=d,this.id=e,this.options=h||{matchBare:!1},this.options.matchBare||(this.options.matchBare=!1),this.from=this.options.matchBare?g?f.getBareJidFromJid(g):null:g,this.user=!0},f.Handler.prototype={isMatch:function(a){var b,c=null;if(c=this.options.matchBare?f.getBareJidFromJid(a.getAttribute("from")):a.getAttribute("from"),b=!1,this.ns){var d=this;f.forEachChild(a,null,function(a){a.getAttribute("xmlns")==d.ns&&(b=!0)}),b=b||a.getAttribute("xmlns")==this.ns}else b=!0;return!b||this.name&&!f.isTagEqual(a,this.name)||this.type&&a.getAttribute("type")!=this.type||this.id&&a.getAttribute("id")!=this.id||this.from&&c!=this.from?!1:!0},run:function(a){var b=null;try{b=this.handler(a)}catch(c){throw c.sourceURL?f.fatal("error: "+this.handler+" "+c.sourceURL+":"+c.line+" - "+c.name+": "+c.message):c.fileName?("undefined"!=typeof console&&(console.trace(),console.error(this.handler," - error - ",c,c.message)),f.fatal("error: "+this.handler+" "+c.fileName+":"+c.lineNumber+" - "+c.name+": "+c.message)):f.fatal("error: "+c.message+"\n"+c.stack),c}return b},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}},f.TimedHandler=function(a,b){this.period=a,this.handler=b,this.lastCalled=(new Date).getTime(),this.user=!0},f.TimedHandler.prototype={run:function(){return this.lastCalled=(new Date).getTime(),this.handler()},reset:function(){this.lastCalled=(new Date).getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}},f.Connection=function(a,b){this.service=a,this.options=b||{};var c=this.options.protocol||"";this._proto=0===a.indexOf("ws:")||0===a.indexOf("wss:")||0===c.indexOf("ws")?new f.Websocket(this):new f.Bosh(this),this.jid="",this.domain=null,this.features=null,this._sasl_data={},this.do_session=!1,this.do_bind=!1,this.timedHandlers=[],this.handlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._authentication={},this._idleTimeout=null,this._disconnectTimeout=null,this.do_authentication=!0,this.authenticated=!1,this.disconnecting=!1,this.connected=!1,this.errors=0,this.paused=!1,this._data=[],this._uniqueId=0,this._sasl_success_handler=null,this._sasl_failure_handler=null,this._sasl_challenge_handler=null,this.maxRetries=5,this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var d in f._connectionPlugins)if(f._connectionPlugins.hasOwnProperty(d)){var e=f._connectionPlugins[d],g=function(){};g.prototype=e,this[d]=new g,this[d].init(this)}},f.Connection.prototype={reset:function(){this._proto._reset(),this.do_session=!1,this.do_bind=!1,this.timedHandlers=[],this.handlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._authentication={},this.authenticated=!1,this.disconnecting=!1,this.connected=!1,this.errors=0,this._requests=[],this._uniqueId=0},pause:function(){this.paused=!0},resume:function(){this.paused=!1},getUniqueId:function(a){return"string"==typeof a||"number"==typeof a?++this._uniqueId+":"+a:++this._uniqueId+""},connect:function(a,b,c,d,e,g){this.jid=a,this.authzid=f.getBareJidFromJid(this.jid),this.authcid=f.getNodeFromJid(this.jid),this.pass=b,this.servtype="xmpp",this.connect_callback=c,this.disconnecting=!1,this.connected=!1,this.authenticated=!1,this.errors=0,this.domain=f.getDomainFromJid(this.jid),this._changeConnectStatus(f.Status.CONNECTING,null),this._proto._connect(d,e,g)},attach:function(a,b,c,d,e,f,g){this._proto._attach(a,b,c,d,e,f,g)},xmlInput:function(){},xmlOutput:function(){},rawInput:function(){},rawOutput:function(){},send:function(a){if(null!==a){if("function"==typeof a.sort)for(var b=0;b<a.length;b++)this._queueData(a[b]);else"function"==typeof a.tree?this._queueData(a.tree()):this._queueData(a);this._proto._send()}},flush:function(){clearTimeout(this._idleTimeout),this._onIdle()},sendIQ:function(a,b,c,d){var e=null,f=this;"function"==typeof a.tree&&(a=a.tree());var g=a.getAttribute("id");g||(g=this.getUniqueId("sendIQ"),a.setAttribute("id",g));var h=this.addHandler(function(a){e&&f.deleteTimedHandler(e);var d=a.getAttribute("type");if("result"==d)b&&b(a);else{if("error"!=d)throw{name:"StropheError",message:"Got bad IQ type of "+d};c&&c(a)}},null,"iq",null,g);return d&&(e=this.addTimedHandler(d,function(){return f.deleteHandler(h),c&&c(null),!1})),this.send(a),g},_queueData:function(a){if(null===a||!a.tagName||!a.childNodes)throw{name:"StropheError",message:"Cannot queue non-DOMElement."};this._data.push(a)},_sendRestart:function(){this._data.push("restart"),this._proto._sendRestart(),this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},addTimedHandler:function(a,b){var c=new f.TimedHandler(a,b);return this.addTimeds.push(c),c},deleteTimedHandler:function(a){this.removeTimeds.push(a)},addHandler:function(a,b,c,d,e,g,h){var i=new f.Handler(a,b,c,d,e,g,h);return this.addHandlers.push(i),i},deleteHandler:function(a){this.removeHandlers.push(a)},disconnect:function(a){if(this._changeConnectStatus(f.Status.DISCONNECTING,a),f.info("Disconnect was called because: "+a),this.connected){var b=!1;this.disconnecting=!0,this.authenticated&&(b=e({xmlns:f.NS.CLIENT,type:"unavailable"})),this._disconnectTimeout=this._addSysTimedHandler(3e3,this._onDisconnectTimeout.bind(this)),this._proto._disconnect(b)}},_changeConnectStatus:function(a,b){for(var c in f._connectionPlugins)if(f._connectionPlugins.hasOwnProperty(c)){var d=this[c];if(d.statusChanged)try{d.statusChanged(a,b)}catch(e){f.error(""+c+" plugin caused an exception changing status: "+e)}}if(this.connect_callback)try{this.connect_callback(a,b)}catch(g){f.error("User connection callback caused an exception: "+g)}},_doDisconnect:function(){null!==this._disconnectTimeout&&(this.deleteTimedHandler(this._disconnectTimeout),this._disconnectTimeout=null),f.info("_doDisconnect was called"),this._proto._doDisconnect(),this.authenticated=!1,this.disconnecting=!1,this.handlers=[],this.timedHandlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._changeConnectStatus(f.Status.DISCONNECTED,null),this.connected=!1},_dataRecv:function(a,b){f.info("_dataRecv called");var c=this._proto._reqToData(a);if(null!==c){this.xmlInput!==f.Connection.prototype.xmlInput&&(c.nodeName===this._proto.strip&&c.childNodes.length?this.xmlInput(c.childNodes[0]):this.xmlInput(c)),this.rawInput!==f.Connection.prototype.rawInput&&(b?this.rawInput(b):this.rawInput(f.serialize(c)));for(var d,e;this.removeHandlers.length>0;)e=this.removeHandlers.pop(),d=this.handlers.indexOf(e),d>=0&&this.handlers.splice(d,1);for(;this.addHandlers.length>0;)this.handlers.push(this.addHandlers.pop());if(this.disconnecting&&this._proto._emptyQueue())return this._doDisconnect(),void 0;var g,h,i=c.getAttribute("type");if(null!==i&&"terminate"==i){if(this.disconnecting)return;return g=c.getAttribute("condition"),h=c.getElementsByTagName("conflict"),null!==g?("remote-stream-error"==g&&h.length>0&&(g="conflict"),this._changeConnectStatus(f.Status.CONNFAIL,g)):this._changeConnectStatus(f.Status.CONNFAIL,"unknown"),this.disconnect("unknown stream-error"),void 0}var j=this;f.forEachChild(c,null,function(a){var b,c;for(c=j.handlers,j.handlers=[],b=0;b<c.length;b++){var d=c[b];try{!d.isMatch(a)||!j.authenticated&&d.user?j.handlers.push(d):d.run(a)&&j.handlers.push(d)}catch(e){f.warn("Removing Strophe handlers due to uncaught exception: "+e.message)}}})}},mechanisms:{},_connect_cb:function(a,b,c){f.info("_connect_cb was called"),this.connected=!0;var d=this._proto._reqToData(a);if(d){this.xmlInput!==f.Connection.prototype.xmlInput&&(d.nodeName===this._proto.strip&&d.childNodes.length?this.xmlInput(d.childNodes[0]):this.xmlInput(d)),this.rawInput!==f.Connection.prototype.rawInput&&(c?this.rawInput(c):this.rawInput(f.serialize(d)));var e=this._proto._connect_cb(d);if(e!==f.Status.CONNFAIL){this._authentication.sasl_scram_sha1=!1,this._authentication.sasl_plain=!1,this._authentication.sasl_digest_md5=!1,this._authentication.sasl_anonymous=!1,this._authentication.legacy_auth=!1;var g=d.getElementsByTagName("stream:features").length>0;g||(g=d.getElementsByTagName("features").length>0);var h,i,j=d.getElementsByTagName("mechanism"),k=[],l=!1;if(!g)return this._proto._no_auth_received(b),void 0;if(j.length>0)for(h=0;h<j.length;h++)i=f.getText(j[h]),this.mechanisms[i]&&k.push(this.mechanisms[i]);return this._authentication.legacy_auth=d.getElementsByTagName("auth").length>0,(l=this._authentication.legacy_auth||k.length>0)?(this.do_authentication!==!1&&this.authenticate(k),void 0):(this._proto._no_auth_received(b),void 0)}}},authenticate:function(a){var c;for(c=0;c<a.length-1;++c){for(var e=c,g=c+1;g<a.length;++g)a[g].prototype.priority>a[e].prototype.priority&&(e=g);if(e!=c){var h=a[c];a[c]=a[e],a[e]=h}}var i=!1;for(c=0;c<a.length;++c)if(a[c].test(this)){this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null),this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null),this._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge_cb.bind(this),null,"challenge",null,null),this._sasl_mechanism=new a[c],this._sasl_mechanism.onStart(this);var j=b("auth",{xmlns:f.NS.SASL,mechanism:this._sasl_mechanism.name});if(this._sasl_mechanism.isClientFirst){var k=this._sasl_mechanism.onChallenge(this,null);j.t(Base64.encode(k))}this.send(j.tree()),i=!0;break}i||(null===f.getNodeFromJid(this.jid)?(this._changeConnectStatus(f.Status.CONNFAIL,"x-strophe-bad-non-anon-jid"),this.disconnect("x-strophe-bad-non-anon-jid")):(this._changeConnectStatus(f.Status.AUTHENTICATING,null),this._addSysHandler(this._auth1_cb.bind(this),null,null,null,"_auth_1"),this.send(d({type:"get",to:this.domain,id:"_auth_1"}).c("query",{xmlns:f.NS.AUTH}).c("username",{}).t(f.getNodeFromJid(this.jid)).tree())))},_sasl_challenge_cb:function(a){var c=Base64.decode(f.getText(a)),d=this._sasl_mechanism.onChallenge(this,c),e=b("response",{xmlns:f.NS.SASL});return""!==d&&e.t(Base64.encode(d)),this.send(e.tree()),!0},_auth1_cb:function(){var a=d({type:"set",id:"_auth_2"}).c("query",{xmlns:f.NS.AUTH}).c("username",{}).t(f.getNodeFromJid(this.jid)).up().c("password").t(this.pass);return f.getResourceFromJid(this.jid)||(this.jid=f.getBareJidFromJid(this.jid)+"/strophe"),a.up().c("resource",{}).t(f.getResourceFromJid(this.jid)),this._addSysHandler(this._auth2_cb.bind(this),null,null,null,"_auth_2"),this.send(a.tree()),!1},_sasl_success_cb:function(a){if(this._sasl_data["server-signature"]){var b,c=Base64.decode(f.getText(a)),d=/([a-z]+)=([^,]+)(,|$)/,e=c.match(d);if("v"==e[1]&&(b=e[2]),b!=this._sasl_data["server-signature"])return this.deleteHandler(this._sasl_failure_handler),this._sasl_failure_handler=null,this._sasl_challenge_handler&&(this.deleteHandler(this._sasl_challenge_handler),this._sasl_challenge_handler=null),this._sasl_data={},this._sasl_failure_cb(null)}return f.info("SASL authentication succeeded."),this._sasl_mechanism&&this._sasl_mechanism.onSuccess(),this.deleteHandler(this._sasl_failure_handler),this._sasl_failure_handler=null,this._sasl_challenge_handler&&(this.deleteHandler(this._sasl_challenge_handler),this._sasl_challenge_handler=null),this._addSysHandler(this._sasl_auth1_cb.bind(this),null,"stream:features",null,null),this._sendRestart(),!1},_sasl_auth1_cb:function(a){this.features=a;var b,c;for(b=0;b<a.childNodes.length;b++)c=a.childNodes[b],"bind"==c.nodeName&&(this.do_bind=!0),"session"==c.nodeName&&(this.do_session=!0);if(!this.do_bind)return this._changeConnectStatus(f.Status.AUTHFAIL,null),!1;this._addSysHandler(this._sasl_bind_cb.bind(this),null,null,null,"_bind_auth_2");var e=f.getResourceFromJid(this.jid);return e?this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:f.NS.BIND}).c("resource",{}).t(e).tree()):this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:f.NS.BIND}).tree()),!1},_sasl_bind_cb:function(a){if("error"==a.getAttribute("type")){f.info("SASL binding failed.");var b,c=a.getElementsByTagName("conflict");return c.length>0&&(b="conflict"),this._changeConnectStatus(f.Status.AUTHFAIL,b),!1}var e,g=a.getElementsByTagName("bind");return g.length>0?(e=g[0].getElementsByTagName("jid"),e.length>0&&(this.jid=f.getText(e[0]),this.do_session?(this._addSysHandler(this._sasl_session_cb.bind(this),null,null,null,"_session_auth_2"),this.send(d({type:"set",id:"_session_auth_2"}).c("session",{xmlns:f.NS.SESSION}).tree())):(this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null))),void 0):(f.info("SASL binding failed."),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1)},_sasl_session_cb:function(a){if("result"==a.getAttribute("type"))this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null);else if("error"==a.getAttribute("type"))return f.info("Session creation failed."),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1;return!1},_sasl_failure_cb:function(){return this._sasl_success_handler&&(this.deleteHandler(this._sasl_success_handler),this._sasl_success_handler=null),this._sasl_challenge_handler&&(this.deleteHandler(this._sasl_challenge_handler),this._sasl_challenge_handler=null),this._sasl_mechanism&&this._sasl_mechanism.onFailure(),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1},_auth2_cb:function(a){return"result"==a.getAttribute("type")?(this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null)):"error"==a.getAttribute("type")&&(this._changeConnectStatus(f.Status.AUTHFAIL,null),this.disconnect("authentication failed")),!1},_addSysTimedHandler:function(a,b){var c=new f.TimedHandler(a,b);return c.user=!1,this.addTimeds.push(c),c},_addSysHandler:function(a,b,c,d,e){var g=new f.Handler(a,b,c,d,e);return g.user=!1,this.addHandlers.push(g),g},_onDisconnectTimeout:function(){return f.info("_onDisconnectTimeout was called"),this._proto._onDisconnectTimeout(),this._doDisconnect(),!1},_onIdle:function(){for(var a,b,c,d;this.addTimeds.length>0;)this.timedHandlers.push(this.addTimeds.pop());for(;this.removeTimeds.length>0;)b=this.removeTimeds.pop(),a=this.timedHandlers.indexOf(b),a>=0&&this.timedHandlers.splice(a,1);var e=(new Date).getTime();for(d=[],a=0;a<this.timedHandlers.length;a++)b=this.timedHandlers[a],(this.authenticated||!b.user)&&(c=b.lastCalled+b.period,0>=c-e?b.run()&&d.push(b):d.push(b));this.timedHandlers=d,clearTimeout(this._idleTimeout),this._proto._onIdle(),this.connected&&(this._idleTimeout=setTimeout(this._onIdle.bind(this),100))}},a&&a(f,b,c,d,e),f.SASLMechanism=function(a,b,c){this.name=a,this.isClientFirst=b,this.priority=c},f.SASLMechanism.prototype={test:function(){return!0},onStart:function(a){this._connection=a},onChallenge:function(){throw new Error("You should implement challenge handling!")},onFailure:function(){this._connection=null},onSuccess:function(){this._connection=null}},f.SASLAnonymous=function(){},f.SASLAnonymous.prototype=new f.SASLMechanism("ANONYMOUS",!1,10),f.SASLAnonymous.test=function(a){return null===a.authcid},f.Connection.prototype.mechanisms[f.SASLAnonymous.prototype.name]=f.SASLAnonymous,f.SASLPlain=function(){},f.SASLPlain.prototype=new f.SASLMechanism("PLAIN",!0,20),f.SASLPlain.test=function(a){return null!==a.authcid},f.SASLPlain.prototype.onChallenge=function(a){var b=a.authzid;return b+="\x00",b+=a.authcid,b+="\x00",b+=a.pass},f.Connection.prototype.mechanisms[f.SASLPlain.prototype.name]=f.SASLPlain,f.SASLSHA1=function(){},f.SASLSHA1.prototype=new f.SASLMechanism("SCRAM-SHA-1",!0,40),f.SASLSHA1.test=function(a){return null!==a.authcid},f.SASLSHA1.prototype.onChallenge=function(a,b,c){var d=c||MD5.hexdigest(1234567890*Math.random()),e="n="+a.authcid;return e+=",r=",e+=d,a._sasl_data.cnonce=d,a._sasl_data["client-first-message-bare"]=e,e="n,,"+e,this.onChallenge=function(a,b){for(var c,d,e,f,g,h,i,j,k,l,m,n="c=biws,",o=a._sasl_data["client-first-message-bare"]+","+b+",",p=a._sasl_data.cnonce,q=/([a-z]+)=([^,]+)(,|$)/;b.match(q);){var r=b.match(q);switch(b=b.replace(r[0],""),r[1]){case"r":c=r[2];break;case"s":d=r[2];break;case"i":e=r[2]}}if(c.substr(0,p.length)!==p)return a._sasl_data={},a._sasl_failure_cb();for(n+="r="+c,o+=n,d=Base64.decode(d),d+="\x00\x00\x00",f=h=core_hmac_sha1(a.pass,d),i=1;e>i;i++){for(g=core_hmac_sha1(a.pass,binb2str(h)),j=0;5>j;j++)f[j]^=g[j];h=g}for(f=binb2str(f),k=core_hmac_sha1(f,"Client Key"),l=str_hmac_sha1(f,"Server Key"),m=core_hmac_sha1(str_sha1(binb2str(k)),o),a._sasl_data["server-signature"]=b64_hmac_sha1(l,o),j=0;5>j;j++)k[j]^=m[j];return n+=",p="+Base64.encode(binb2str(k))}.bind(this),e},f.Connection.prototype.mechanisms[f.SASLSHA1.prototype.name]=f.SASLSHA1,f.SASLMD5=function(){},f.SASLMD5.prototype=new f.SASLMechanism("DIGEST-MD5",!1,30),f.SASLMD5.test=function(a){return null!==a.authcid},f.SASLMD5.prototype._quote=function(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'},f.SASLMD5.prototype.onChallenge=function(a,b,c){for(var d,e=/([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/,f=c||MD5.hexdigest(""+1234567890*Math.random()),g="",h=null,i="",j="";b.match(e);)switch(d=b.match(e),b=b.replace(d[0],""),d[2]=d[2].replace(/^"(.+)"$/,"$1"),d[1]){case"realm":g=d[2];
  3. break;case"nonce":i=d[2];break;case"qop":j=d[2];break;case"host":h=d[2]}var k=a.servtype+"/"+a.domain;null!==h&&(k=k+"/"+h);var l=MD5.hash(a.authcid+":"+g+":"+this._connection.pass)+":"+i+":"+f,m="AUTHENTICATE:"+k,n="";return n+="charset=utf-8,",n+="username="+this._quote(a.authcid)+",",n+="realm="+this._quote(g)+",",n+="nonce="+this._quote(i)+",",n+="nc=00000001,",n+="cnonce="+this._quote(f)+",",n+="digest-uri="+this._quote(k)+",",n+="response="+MD5.hexdigest(MD5.hexdigest(l)+":"+i+":00000001:"+f+":auth:"+MD5.hexdigest(m))+",",n+="qop=auth",this.onChallenge=function(){return""}.bind(this),n},f.Connection.prototype.mechanisms[f.SASLMD5.prototype.name]=f.SASLMD5}(function(){window.Strophe=arguments[0],window.$build=arguments[1],window.$msg=arguments[2],window.$iq=arguments[3],window.$pres=arguments[4]}),Strophe.Request=function(a,b,c,d){this.id=++Strophe._requestId,this.xmlData=a,this.data=Strophe.serialize(a),this.origFunc=b,this.func=b,this.rid=c,this.date=0/0,this.sends=d||0,this.abort=!1,this.dead=null,this.age=function(){if(!this.date)return 0;var a=new Date;return(a-this.date)/1e3},this.timeDead=function(){if(!this.dead)return 0;var a=new Date;return(a-this.dead)/1e3},this.xhr=this._newXHR()},Strophe.Request.prototype={getResponse:function(){var a=null;if(this.xhr.responseXML&&this.xhr.responseXML.documentElement){if(a=this.xhr.responseXML.documentElement,"parsererror"==a.tagName)throw Strophe.error("invalid response received"),Strophe.error("responseText: "+this.xhr.responseText),Strophe.error("responseXML: "+Strophe.serialize(this.xhr.responseXML)),"parsererror"}else this.xhr.responseText&&(Strophe.error("invalid response received"),Strophe.error("responseText: "+this.xhr.responseText),Strophe.error("responseXML: "+Strophe.serialize(this.xhr.responseXML)));return a},_newXHR:function(){var a=null;return window.XMLHttpRequest?(a=new XMLHttpRequest,a.overrideMimeType&&a.overrideMimeType("text/xml")):window.ActiveXObject&&(a=new ActiveXObject("Microsoft.XMLHTTP")),a.onreadystatechange=this.func.bind(null,this),a}},Strophe.Bosh=function(a){this._conn=a,this.rid=Math.floor(4294967295*Math.random()),this.sid=null,this.hold=1,this.wait=60,this.window=5,this._requests=[]},Strophe.Bosh.prototype={strip:null,_buildBody:function(){var a=$build("body",{rid:this.rid++,xmlns:Strophe.NS.HTTPBIND});return null!==this.sid&&a.attrs({sid:this.sid}),a},_reset:function(){this.rid=Math.floor(4294967295*Math.random()),this.sid=null},_connect:function(a,b,c){this.wait=a||this.wait,this.hold=b||this.hold;var d=this._buildBody().attrs({to:this._conn.domain,"xml:lang":"en",wait:this.wait,hold:this.hold,content:"text/xml; charset=utf-8",ver:"1.6","xmpp:version":"1.0","xmlns:xmpp":Strophe.NS.BOSH});c&&d.attrs({route:c});var e=this._conn._connect_cb;this._requests.push(new Strophe.Request(d.tree(),this._onRequestStateChange.bind(this,e.bind(this._conn)),d.tree().getAttribute("rid"))),this._throttledRequestHandler()},_attach:function(a,b,c,d,e,f,g){this._conn.jid=a,this.sid=b,this.rid=c,this._conn.connect_callback=d,this._conn.domain=Strophe.getDomainFromJid(this._conn.jid),this._conn.authenticated=!0,this._conn.connected=!0,this.wait=e||this.wait,this.hold=f||this.hold,this.window=g||this.window,this._conn._changeConnectStatus(Strophe.Status.ATTACHED,null)},_connect_cb:function(a){var b,c,d=a.getAttribute("type");if(null!==d&&"terminate"==d)return Strophe.error("BOSH-Connection failed: "+b),b=a.getAttribute("condition"),c=a.getElementsByTagName("conflict"),null!==b?("remote-stream-error"==b&&c.length>0&&(b="conflict"),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,b)):this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown"),this._conn._doDisconnect(),Strophe.Status.CONNFAIL;this.sid||(this.sid=a.getAttribute("sid"));var e=a.getAttribute("requests");e&&(this.window=parseInt(e,10));var f=a.getAttribute("hold");f&&(this.hold=parseInt(f,10));var g=a.getAttribute("wait");g&&(this.wait=parseInt(g,10))},_disconnect:function(a){this._sendTerminate(a)},_doDisconnect:function(){this.sid=null,this.rid=Math.floor(4294967295*Math.random())},_emptyQueue:function(){return 0===this._requests.length},_hitError:function(a){this.errors++,Strophe.warn("request errored, status: "+a+", number of errors: "+this.errors),this.errors>4&&this._onDisconnectTimeout()},_no_auth_received:function(a){a=a?a.bind(this._conn):this._conn._connect_cb.bind(this._conn);var b=this._buildBody();this._requests.push(new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,a.bind(this._conn)),b.tree().getAttribute("rid"))),this._throttledRequestHandler()},_onDisconnectTimeout:function(){for(var a;this._requests.length>0;)a=this._requests.pop(),a.abort=!0,a.xhr.abort(),a.xhr.onreadystatechange=function(){}},_onIdle:function(){var a=this._conn._data;if(this._conn.authenticated&&0===this._requests.length&&0===a.length&&!this._conn.disconnecting&&(Strophe.info("no requests during idle cycle, sending blank request"),a.push(null)),this._requests.length<2&&a.length>0&&!this._conn.paused){for(var b=this._buildBody(),c=0;c<a.length;c++)null!==a[c]&&("restart"===a[c]?b.attrs({to:this._conn.domain,"xml:lang":"en","xmpp:restart":"true","xmlns:xmpp":Strophe.NS.BOSH}):b.cnode(a[c]).up());delete this._conn._data,this._conn._data=[],this._requests.push(new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),b.tree().getAttribute("rid"))),this._processRequest(this._requests.length-1)}if(this._requests.length>0){var d=this._requests[0].age();null!==this._requests[0].dead&&this._requests[0].timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait)&&this._throttledRequestHandler(),d>Math.floor(Strophe.TIMEOUT*this.wait)&&(Strophe.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(Strophe.TIMEOUT*this.wait)+" seconds since last activity"),this._throttledRequestHandler())}},_onRequestStateChange:function(a,b){if(Strophe.debug("request id "+b.id+"."+b.sends+" state changed to "+b.xhr.readyState),b.abort)return b.abort=!1,void 0;var c;if(4==b.xhr.readyState){c=0;try{c=b.xhr.status}catch(d){}if("undefined"==typeof c&&(c=0),this.disconnecting&&c>=400)return this._hitError(c),void 0;var e=this._requests[0]==b,f=this._requests[1]==b;(c>0&&500>c||b.sends>5)&&(this._removeRequest(b),Strophe.debug("request id "+b.id+" should now be removed")),200==c?((f||e&&this._requests.length>0&&this._requests[0].age()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait))&&this._restartRequest(0),Strophe.debug("request id "+b.id+"."+b.sends+" got 200"),a(b),this.errors=0):(Strophe.error("request id "+b.id+"."+b.sends+" error "+c+" happened"),(0===c||c>=400&&600>c||c>=12e3)&&(this._hitError(c),c>=400&&500>c&&(this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING,null),this._conn._doDisconnect()))),c>0&&500>c||b.sends>5||this._throttledRequestHandler()}},_processRequest:function(a){var b=this,c=this._requests[a],d=-1;try{4==c.xhr.readyState&&(d=c.xhr.status)}catch(e){Strophe.error("caught an error in _requests["+a+"], reqStatus: "+d)}if("undefined"==typeof d&&(d=-1),c.sends>this.maxRetries)return this._onDisconnectTimeout(),void 0;var f=c.age(),g=!isNaN(f)&&f>Math.floor(Strophe.TIMEOUT*this.wait),h=null!==c.dead&&c.timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait),i=4==c.xhr.readyState&&(1>d||d>=500);if((g||h||i)&&(h&&Strophe.error("Request "+this._requests[a].id+" timed out (secondary), restarting"),c.abort=!0,c.xhr.abort(),c.xhr.onreadystatechange=function(){},this._requests[a]=new Strophe.Request(c.xmlData,c.origFunc,c.rid,c.sends),c=this._requests[a]),0===c.xhr.readyState){Strophe.debug("request id "+c.id+"."+c.sends+" posting");try{c.xhr.open("POST",this._conn.service,this._conn.options.sync?!1:!0)}catch(j){return Strophe.error("XHR open failed."),this._conn.connected||this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"bad-service"),this._conn.disconnect(),void 0}var k=function(){if(c.date=new Date,b._conn.options.customHeaders){var a=b._conn.options.customHeaders;for(var d in a)a.hasOwnProperty(d)&&c.xhr.setRequestHeader(d,a[d])}c.xhr.send(c.data)};if(c.sends>1){var l=1e3*Math.min(Math.floor(Strophe.TIMEOUT*this.wait),Math.pow(c.sends,3));setTimeout(k,l)}else k();c.sends++,this._conn.xmlOutput!==Strophe.Connection.prototype.xmlOutput&&(c.xmlData.nodeName===this.strip&&c.xmlData.childNodes.length?this._conn.xmlOutput(c.xmlData.childNodes[0]):this._conn.xmlOutput(c.xmlData)),this._conn.rawOutput!==Strophe.Connection.prototype.rawOutput&&this._conn.rawOutput(c.data)}else Strophe.debug("_processRequest: "+(0===a?"first":"second")+" request has readyState of "+c.xhr.readyState)},_removeRequest:function(a){Strophe.debug("removing request");var b;for(b=this._requests.length-1;b>=0;b--)a==this._requests[b]&&this._requests.splice(b,1);a.xhr.onreadystatechange=function(){},this._throttledRequestHandler()},_restartRequest:function(a){var b=this._requests[a];null===b.dead&&(b.dead=new Date),this._processRequest(a)},_reqToData:function(a){try{return a.getResponse()}catch(b){if("parsererror"!=b)throw b;this._conn.disconnect("strophe-parsererror")}},_sendTerminate:function(a){Strophe.info("_sendTerminate was called");var b=this._buildBody().attrs({type:"terminate"});a&&b.cnode(a.tree());var c=new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),b.tree().getAttribute("rid"));this._requests.push(c),this._throttledRequestHandler()},_send:function(){clearTimeout(this._conn._idleTimeout),this._throttledRequestHandler(),this._conn._idleTimeout=setTimeout(this._conn._onIdle.bind(this._conn),100)},_sendRestart:function(){this._throttledRequestHandler(),clearTimeout(this._conn._idleTimeout)},_throttledRequestHandler:function(){this._requests?Strophe.debug("_throttledRequestHandler called with "+this._requests.length+" requests"):Strophe.debug("_throttledRequestHandler called with undefined requests"),this._requests&&0!==this._requests.length&&(this._requests.length>0&&this._processRequest(0),this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)<this.window&&this._processRequest(1))}},Strophe.Websocket=function(a){this._conn=a,this.strip="stream:stream";var b=a.service;if(0!==b.indexOf("ws:")&&0!==b.indexOf("wss:")){var c="";c+="ws"===a.options.protocol&&"https:"!==window.location.protocol?"ws":"wss",c+="://"+window.location.host,c+=0!==b.indexOf("/")?window.location.pathname+b:b,a.service=c}},Strophe.Websocket.prototype={_buildStream:function(){return $build("stream:stream",{to:this._conn.domain,xmlns:Strophe.NS.CLIENT,"xmlns:stream":Strophe.NS.STREAM,version:"1.0"})},_check_streamerror:function(a,b){var c=a.getElementsByTagName("stream:error");if(0===c.length)return!1;for(var d=c[0],e="",f="",g="urn:ietf:params:xml:ns:xmpp-streams",h=0;h<d.childNodes.length;h++){var i=d.childNodes[h];if(i.getAttribute("xmlns")!==g)break;"text"===i.nodeName?f=i.textContent:e=i.nodeName}var j="WebSocket stream error: ";return j+=e?e:"unknown",f&&(j+=" - "+e),Strophe.error(j),this._conn._changeConnectStatus(b,e),this._conn._doDisconnect(),!0},_reset:function(){},_connect:function(){this._closeSocket(),this.socket=new WebSocket(this._conn.service,"xmpp"),this.socket.onopen=this._onOpen.bind(this),this.socket.onerror=this._onError.bind(this),this.socket.onclose=this._onClose.bind(this),this.socket.onmessage=this._connect_cb_wrapper.bind(this)},_connect_cb:function(a){var b=this._check_streamerror(a,Strophe.Status.CONNFAIL);return b?Strophe.Status.CONNFAIL:void 0},_handleStreamStart:function(a){var b=!1,c=a.getAttribute("xmlns");"string"!=typeof c?b="Missing xmlns in stream:stream":c!==Strophe.NS.CLIENT&&(b="Wrong xmlns in stream:stream: "+c);var d=a.namespaceURI;"string"!=typeof d?b="Missing xmlns:stream in stream:stream":d!==Strophe.NS.STREAM&&(b="Wrong xmlns:stream in stream:stream: "+d);var e=a.getAttribute("version");return"string"!=typeof e?b="Missing version in stream:stream":"1.0"!==e&&(b="Wrong version in stream:stream: "+e),b?(this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,b),this._conn._doDisconnect(),!1):!0},_connect_cb_wrapper:function(a){if(0===a.data.indexOf("<stream:stream ")||0===a.data.indexOf("<?xml")){var b=a.data.replace(/^(<\?.*?\?>\s*)*/,"");if(""===b)return;b=a.data.replace(/<stream:stream (.*[^\/])>/,"<stream:stream $1/>");var c=(new DOMParser).parseFromString(b,"text/xml").documentElement;this._conn.xmlInput(c),this._conn.rawInput(a.data),this._handleStreamStart(c)&&(this._connect_cb(c),this.streamStart=a.data.replace(/^<stream:(.*)\/>$/,"<stream:$1>"))}else{if("</stream:stream>"===a.data)return this._conn.rawInput(a.data),this._conn.xmlInput(document.createElement("stream:stream")),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Received closing stream"),this._conn._doDisconnect(),void 0;var d=this._streamWrap(a.data),e=(new DOMParser).parseFromString(d,"text/xml").documentElement;this.socket.onmessage=this._onMessage.bind(this),this._conn._connect_cb(e,null,a.data)}},_disconnect:function(a){if(this.socket.readyState!==WebSocket.CLOSED){a&&this._conn.send(a);var b="</stream:stream>";this._conn.xmlOutput(document.createElement("stream:stream")),this._conn.rawOutput(b);try{this.socket.send(b)}catch(c){Strophe.info("Couldn't send closing stream tag.")}}this._conn._doDisconnect()},_doDisconnect:function(){Strophe.info("WebSockets _doDisconnect was called"),this._closeSocket()},_streamWrap:function(a){return this.streamStart+a+"</stream:stream>"},_closeSocket:function(){if(this.socket)try{this.socket.close()}catch(a){}this.socket=null},_emptyQueue:function(){return!0},_onClose:function(){this._conn.connected&&!this._conn.disconnecting?(Strophe.error("Websocket closed unexcectedly"),this._conn._doDisconnect()):Strophe.info("Websocket closed")},_no_auth_received:function(a){Strophe.error("Server did not send any auth methods"),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Server did not send any auth methods"),a&&(a=a.bind(this._conn))(),this._conn._doDisconnect()},_onDisconnectTimeout:function(){},_onError:function(a){Strophe.error("Websocket error "+a),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"The WebSocket connection could not be established was disconnected."),this._disconnect()},_onIdle:function(){var a=this._conn._data;if(a.length>0&&!this._conn.paused){for(var b=0;b<a.length;b++)if(null!==a[b]){var c,d;"restart"===a[b]?(c=this._buildStream(),d=this._removeClosingTag(c),c=c.tree()):(c=a[b],d=Strophe.serialize(c)),this._conn.xmlOutput(c),this._conn.rawOutput(d),this.socket.send(d)}this._conn._data=[]}},_onMessage:function(a){var b,c;if("</stream:stream>"===a.data){var d="</stream:stream>";return this._conn.rawInput(d),this._conn.xmlInput(document.createElement("stream:stream")),this._conn.disconnecting||this._conn._doDisconnect(),void 0}if(0===a.data.search("<stream:stream ")){if(c=a.data.replace(/<stream:stream (.*[^\/])>/,"<stream:stream $1/>"),b=(new DOMParser).parseFromString(c,"text/xml").documentElement,!this._handleStreamStart(b))return}else c=this._streamWrap(a.data),b=(new DOMParser).parseFromString(c,"text/xml").documentElement;if(!this._check_streamerror(b,Strophe.Status.ERROR))return this._conn.disconnecting&&"presence"===b.firstChild.nodeName&&"unavailable"===b.firstChild.getAttribute("type")?(this._conn.xmlInput(b),this._conn.rawInput(Strophe.serialize(b)),void 0):(this._conn._dataRecv(b,a.data),void 0)},_onOpen:function(){Strophe.info("Websocket open");var a=this._buildStream();this._conn.xmlOutput(a.tree());var b=this._removeClosingTag(a);this._conn.rawOutput(b),this.socket.send(b)},_removeClosingTag:function(a){var b=Strophe.serialize(a);return b=b.replace(/<(stream:stream .*[^\/])\/>$/,"<$1>")},_reqToData:function(a){return a},_send:function(){this._conn.flush()},_sendRestart:function(){clearTimeout(this._conn._idleTimeout),this._conn._onIdle.bind(this._conn)()}};Strophe.addConnectionPlugin("disco",{_connection:null,_identities:[],_features:[],_items:[],init:function(a){this._connection=a;this._identities=[];this._features=[];this._items=[];a.addHandler(this._onDiscoInfo.bind(this),Strophe.NS.DISCO_INFO,"iq","get",null,null);a.addHandler(this._onDiscoItems.bind(this),Strophe.NS.DISCO_ITEMS,"iq","get",null,null)},addIdentity:function(d,c,a,e){for(var b=0;b<this._identities.length;b++){if(this._identities[b].category==d&&this._identities[b].type==c&&this._identities[b].name==a&&this._identities[b].lang==e){return false}}this._identities.push({category:d,type:c,name:a,lang:e});return true},addFeature:function(b){for(var a=0;a<this._features.length;a++){if(this._features[a]==b){return false}}this._features.push(b);return true},removeFeature:function(b){for(var a=0;a<this._features.length;a++){if(this._features[a]===b){this._features.splice(a,1);return true}}return false},addItem:function(b,a,c,d){if(c&&!d){return false}this._items.push({jid:b,name:a,node:c,call_back:d});return true},info:function(c,d,g,b,e){var a={xmlns:Strophe.NS.DISCO_INFO};if(d){a.node=d}var f=$iq({from:this._connection.jid,to:c,type:"get"}).c("query",a);this._connection.sendIQ(f,g,b,e)},items:function(d,e,g,c,f){var b={xmlns:Strophe.NS.DISCO_ITEMS};if(e){b.node=e}var a=$iq({from:this._connection.jid,to:d,type:"get"}).c("query",b);this._connection.sendIQ(a,g,c,f)},_buildIQResult:function(c,b){var e=c.getAttribute("id");var d=c.getAttribute("from");var a=$iq({type:"result",id:e});if(d!==null){a.attrs({to:d})}return a.c("query",b)},_onDiscoInfo:function(e){var d=e.getElementsByTagName("query")[0].getAttribute("node");var a={xmlns:Strophe.NS.DISCO_INFO};if(d){a.node=d}var c=this._buildIQResult(e,a);for(var b=0;b<this._identities.length;b++){var a={category:this._identities[b].category,type:this._identities[b].type};if(this._identities[b].name){a.name=this._identities[b].name}if(this._identities[b].lang){a["xml:lang"]=this._identities[b].lang}c.c("identity",a).up()}for(var b=0;b<this._features.length;b++){c.c("feature",{"var":this._features[b]}).up()}this._connection.send(c.tree());return true},_onDiscoItems:function(g){var f={xmlns:Strophe.NS.DISCO_ITEMS};var e=g.getElementsByTagName("query")[0].getAttribute("node");if(e){f.node=e;var a=[];for(var c=0;c<this._items.length;c++){if(this._items[c].node==e){a=this._items[c].call_back(g);break}}}else{var a=this._items}var d=this._buildIQResult(g,f);for(var c=0;c<a.length;c++){var b={jid:a[c].jid};if(a[c].name){b.name=a[c].name}if(a[c].node){b.node=a[c].node}d.c("item",b).up()}this._connection.send(d.tree());return true}});/* jshint -W117 */
  4. function TraceablePeerConnection(ice_config, constraints) {
  5. var self = this;
  6. var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
  7. this.peerconnection = new RTCPeerconnection(ice_config, constraints);
  8. this.updateLog = [];
  9. this.stats = {};
  10. this.statsinterval = null;
  11. this.maxstats = 300; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
  12. // override as desired
  13. this.trace = function(what, info) {
  14. //console.warn('WTRACE', what, info);
  15. self.updateLog.push({
  16. time: new Date(),
  17. type: what,
  18. value: info || ""
  19. });
  20. };
  21. this.onicecandidate = null;
  22. this.peerconnection.onicecandidate = function (event) {
  23. self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
  24. if (self.onicecandidate !== null) {
  25. self.onicecandidate(event);
  26. }
  27. };
  28. this.onaddstream = null;
  29. this.peerconnection.onaddstream = function (event) {
  30. self.trace('onaddstream', event.stream.id);
  31. if (self.onaddstream !== null) {
  32. self.onaddstream(event);
  33. }
  34. };
  35. this.onremovestream = null;
  36. this.peerconnection.onremovestream = function (event) {
  37. self.trace('onremovestream', event.stream.id);
  38. if (self.onremovestream !== null) {
  39. self.onremovestream(event);
  40. }
  41. };
  42. this.onsignalingstatechange = null;
  43. this.peerconnection.onsignalingstatechange = function (event) {
  44. self.trace('onsignalingstatechange', event.srcElement.signalingState);
  45. if (self.onsignalingstatechange !== null) {
  46. self.onsignalingstatechange(event);
  47. }
  48. };
  49. this.oniceconnectionstatechange = null;
  50. this.peerconnection.oniceconnectionstatechange = function (event) {
  51. self.trace('oniceconnectionstatechange', event.srcElement.iceConnectionState);
  52. if (self.oniceconnectionstatechange !== null) {
  53. self.oniceconnectionstatechange(event);
  54. }
  55. };
  56. this.onnegotiationneeded = null;
  57. this.peerconnection.onnegotiationneeded = function (event) {
  58. self.trace('onnegotiationneeded');
  59. if (self.onnegotiationneeded !== null) {
  60. self.onnegotiationneeded(event);
  61. }
  62. };
  63. self.ondatachannel = null;
  64. this.peerconnection.ondatachannel = function (event) {
  65. self.trace('ondatachannel', event);
  66. if (self.ondatachannel !== null) {
  67. self.ondatachannel(event);
  68. }
  69. }
  70. if (!navigator.mozGetUserMedia) {
  71. this.statsinterval = window.setInterval(function() {
  72. self.peerconnection.getStats(function(stats) {
  73. var results = stats.result();
  74. for (var i = 0; i < results.length; ++i) {
  75. //console.log(results[i].type, results[i].id, results[i].names())
  76. var now = new Date();
  77. results[i].names().forEach(function (name) {
  78. var id = results[i].id + '-' + name;
  79. if (!self.stats[id]) {
  80. self.stats[id] = {
  81. startTime: now,
  82. endTime: now,
  83. values: [],
  84. times: []
  85. };
  86. }
  87. self.stats[id].values.push(results[i].stat(name));
  88. self.stats[id].times.push(now.getTime());
  89. if (self.stats[id].values.length > self.maxstats) {
  90. self.stats[id].values.shift();
  91. self.stats[id].times.shift();
  92. }
  93. self.stats[id].endTime = now;
  94. });
  95. }
  96. });
  97. }, 1000);
  98. }
  99. };
  100. dumpSDP = function(description) {
  101. return 'type: ' + description.type + '\r\n' + description.sdp;
  102. }
  103. if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
  104. TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
  105. TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
  106. TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() { return this.peerconnection.localDescription; });
  107. TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() { return this.peerconnection.remoteDescription; });
  108. }
  109. TraceablePeerConnection.prototype.addStream = function (stream) {
  110. this.trace('addStream', stream.id);
  111. this.peerconnection.addStream(stream);
  112. };
  113. TraceablePeerConnection.prototype.removeStream = function (stream) {
  114. this.trace('removeStream', stream.id);
  115. this.peerconnection.removeStream(stream);
  116. };
  117. TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
  118. this.trace('createDataChannel', label, opts);
  119. this.peerconnection.createDataChannel(label, opts);
  120. }
  121. TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
  122. var self = this;
  123. this.trace('setLocalDescription', dumpSDP(description));
  124. this.peerconnection.setLocalDescription(description,
  125. function () {
  126. self.trace('setLocalDescriptionOnSuccess');
  127. successCallback();
  128. },
  129. function (err) {
  130. self.trace('setLocalDescriptionOnFailure', err);
  131. failureCallback(err);
  132. }
  133. );
  134. /*
  135. if (this.statsinterval === null && this.maxstats > 0) {
  136. // start gathering stats
  137. }
  138. */
  139. };
  140. TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
  141. var self = this;
  142. this.trace('setRemoteDescription', dumpSDP(description));
  143. this.peerconnection.setRemoteDescription(description,
  144. function () {
  145. self.trace('setRemoteDescriptionOnSuccess');
  146. successCallback();
  147. },
  148. function (err) {
  149. self.trace('setRemoteDescriptionOnFailure', err);
  150. failureCallback(err);
  151. }
  152. );
  153. /*
  154. if (this.statsinterval === null && this.maxstats > 0) {
  155. // start gathering stats
  156. }
  157. */
  158. };
  159. TraceablePeerConnection.prototype.close = function () {
  160. this.trace('stop');
  161. if (this.statsinterval !== null) {
  162. window.clearInterval(this.statsinterval);
  163. this.statsinterval = null;
  164. }
  165. this.peerconnection.close();
  166. };
  167. TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
  168. var self = this;
  169. this.trace('createOffer', JSON.stringify(constraints, null, ' '));
  170. this.peerconnection.createOffer(
  171. function (offer) {
  172. self.trace('createOfferOnSuccess', dumpSDP(offer));
  173. successCallback(offer);
  174. },
  175. function(err) {
  176. self.trace('createOfferOnFailure', err);
  177. failureCallback(err);
  178. },
  179. constraints
  180. );
  181. };
  182. TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
  183. var self = this;
  184. this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
  185. this.peerconnection.createAnswer(
  186. function (answer) {
  187. self.trace('createAnswerOnSuccess', dumpSDP(answer));
  188. successCallback(answer);
  189. },
  190. function(err) {
  191. self.trace('createAnswerOnFailure', err);
  192. failureCallback(err);
  193. },
  194. constraints
  195. );
  196. };
  197. TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
  198. var self = this;
  199. this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
  200. this.peerconnection.addIceCandidate(candidate);
  201. /* maybe later
  202. this.peerconnection.addIceCandidate(candidate,
  203. function () {
  204. self.trace('addIceCandidateOnSuccess');
  205. successCallback();
  206. },
  207. function (err) {
  208. self.trace('addIceCandidateOnFailure', err);
  209. failureCallback(err);
  210. }
  211. );
  212. */
  213. };
  214. TraceablePeerConnection.prototype.getStats = function(callback, errback) {
  215. if (navigator.mozGetUserMedia) {
  216. // ignore for now...
  217. } else {
  218. this.peerconnection.getStats(callback);
  219. }
  220. };
  221. // mozilla chrome compat layer -- very similar to adapter.js
  222. function setupRTC() {
  223. var RTC = null;
  224. if (navigator.mozGetUserMedia) {
  225. console.log('This appears to be Firefox');
  226. var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
  227. if (version >= 22) {
  228. RTC = {
  229. peerconnection: mozRTCPeerConnection,
  230. browser: 'firefox',
  231. getUserMedia: navigator.mozGetUserMedia.bind(navigator),
  232. attachMediaStream: function (element, stream) {
  233. element[0].mozSrcObject = stream;
  234. element[0].play();
  235. },
  236. pc_constraints: {}
  237. };
  238. if (!MediaStream.prototype.getVideoTracks)
  239. MediaStream.prototype.getVideoTracks = function () { return []; };
  240. if (!MediaStream.prototype.getAudioTracks)
  241. MediaStream.prototype.getAudioTracks = function () { return []; };
  242. RTCSessionDescription = mozRTCSessionDescription;
  243. RTCIceCandidate = mozRTCIceCandidate;
  244. }
  245. } else if (navigator.webkitGetUserMedia) {
  246. console.log('This appears to be Chrome');
  247. RTC = {
  248. peerconnection: webkitRTCPeerConnection,
  249. browser: 'chrome',
  250. getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
  251. attachMediaStream: function (element, stream) {
  252. element.attr('src', webkitURL.createObjectURL(stream));
  253. },
  254. // DTLS should now be enabled by default but..
  255. pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
  256. };
  257. if (navigator.userAgent.indexOf('Android') != -1) {
  258. RTC.pc_constraints = {}; // disable DTLS on Android
  259. }
  260. if (!webkitMediaStream.prototype.getVideoTracks) {
  261. webkitMediaStream.prototype.getVideoTracks = function () {
  262. return this.videoTracks;
  263. };
  264. }
  265. if (!webkitMediaStream.prototype.getAudioTracks) {
  266. webkitMediaStream.prototype.getAudioTracks = function () {
  267. return this.audioTracks;
  268. };
  269. }
  270. }
  271. if (RTC === null) {
  272. try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
  273. }
  274. return RTC;
  275. }
  276. function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
  277. var constraints = {audio: false, video: false};
  278. if (um.indexOf('video') >= 0) {
  279. constraints.video = {mandatory: {}};// same behaviour as true
  280. }
  281. if (um.indexOf('audio') >= 0) {
  282. constraints.audio = {};// same behaviour as true
  283. }
  284. if (um.indexOf('screen') >= 0) {
  285. constraints.video = {
  286. "mandatory": {
  287. "chromeMediaSource": "screen"
  288. }
  289. };
  290. }
  291. if (resolution && !constraints.video) {
  292. constraints.video = {mandatory: {}};// same behaviour as true
  293. }
  294. // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
  295. switch (resolution) {
  296. // 16:9 first
  297. case '1080':
  298. case 'fullhd':
  299. constraints.video.mandatory.minWidth = 1920;
  300. constraints.video.mandatory.minHeight = 1080;
  301. constraints.video.mandatory.minAspectRatio = 1.77;
  302. break;
  303. case '720':
  304. case 'hd':
  305. constraints.video.mandatory.minWidth = 1280;
  306. constraints.video.mandatory.minHeight = 720;
  307. constraints.video.mandatory.minAspectRatio = 1.77;
  308. break;
  309. case '360':
  310. constraints.video.mandatory.minWidth = 640;
  311. constraints.video.mandatory.minHeight = 360;
  312. constraints.video.mandatory.minAspectRatio = 1.77;
  313. break;
  314. case '180':
  315. constraints.video.mandatory.minWidth = 320;
  316. constraints.video.mandatory.minHeight = 180;
  317. constraints.video.mandatory.minAspectRatio = 1.77;
  318. break;
  319. // 4:3
  320. case '960':
  321. constraints.video.mandatory.minWidth = 960;
  322. constraints.video.mandatory.minHeight = 720;
  323. break;
  324. case '640':
  325. case 'vga':
  326. constraints.video.mandatory.minWidth = 640;
  327. constraints.video.mandatory.minHeight = 480;
  328. break;
  329. case '320':
  330. constraints.video.mandatory.minWidth = 320;
  331. constraints.video.mandatory.minHeight = 240;
  332. break;
  333. default:
  334. if (navigator.userAgent.indexOf('Android') != -1) {
  335. constraints.video.mandatory.minWidth = 320;
  336. constraints.video.mandatory.minHeight = 240;
  337. constraints.video.mandatory.maxFrameRate = 15;
  338. }
  339. break;
  340. }
  341. if (bandwidth) { // doesn't work currently, see webrtc issue 1846
  342. if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
  343. constraints.video.optional = [{bandwidth: bandwidth}];
  344. }
  345. if (fps) { // for some cameras it might be necessary to request 30fps
  346. // so they choose 30fps mjpg over 10fps yuy2
  347. if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
  348. constraints.video.mandatory.minFrameRate = fps;
  349. }
  350. try {
  351. RTC.getUserMedia(constraints,
  352. function (stream) {
  353. console.log('onUserMediaSuccess');
  354. $(document).trigger('mediaready.jingle', [stream]);
  355. },
  356. function (error) {
  357. console.warn('Failed to get access to local media. Error ', error);
  358. $(document).trigger('mediafailure.jingle');
  359. });
  360. } catch (e) {
  361. console.error('GUM failed: ', e);
  362. $(document).trigger('mediafailure.jingle');
  363. }
  364. }
  365. /* jshint -W117 */
  366. Strophe.addConnectionPlugin('jingle', {
  367. connection: null,
  368. sessions: {},
  369. jid2session: {},
  370. ice_config: {iceServers: []},
  371. pc_constraints: {},
  372. media_constraints: {
  373. mandatory: {
  374. 'OfferToReceiveAudio': true,
  375. 'OfferToReceiveVideo': true
  376. }
  377. // MozDontOfferDataChannel: true when this is firefox
  378. },
  379. localStream: null,
  380. init: function (conn) {
  381. this.connection = conn;
  382. if (this.connection.disco) {
  383. // http://xmpp.org/extensions/xep-0167.html#support
  384. // http://xmpp.org/extensions/xep-0176.html#support
  385. this.connection.disco.addFeature('urn:xmpp:jingle:1');
  386. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
  387. this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
  388. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
  389. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
  390. // this is dealt with by SDP O/A so we don't need to annouce this
  391. //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
  392. //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
  393. this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
  394. //this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
  395. //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
  396. }
  397. this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
  398. },
  399. onJingle: function (iq) {
  400. var sid = $(iq).find('jingle').attr('sid');
  401. var action = $(iq).find('jingle').attr('action');
  402. // send ack first
  403. var ack = $iq({type: 'result',
  404. to: iq.getAttribute('from'),
  405. id: iq.getAttribute('id')
  406. });
  407. console.log('on jingle ' + action);
  408. var sess = this.sessions[sid];
  409. if ('session-initiate' != action) {
  410. if (sess === null) {
  411. ack.type = 'error';
  412. ack.c('error', {type: 'cancel'})
  413. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  414. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  415. this.connection.send(ack);
  416. return true;
  417. }
  418. // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
  419. // local jid is not checked
  420. if (Strophe.getBareJidFromJid(iq.getAttribute('from')) != Strophe.getBareJidFromJid(sess.peerjid)) {
  421. console.warn('jid mismatch for session id', sid, iq.getAttribute('from'), sess.peerjid);
  422. ack.type = 'error';
  423. ack.c('error', {type: 'cancel'})
  424. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  425. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  426. this.connection.send(ack);
  427. return true;
  428. }
  429. } else if (sess !== undefined) {
  430. // existing session with same session id
  431. // this might be out-of-order if the sess.peerjid is the same as from
  432. ack.type = 'error';
  433. ack.c('error', {type: 'cancel'})
  434. .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
  435. console.warn('duplicate session id', sid);
  436. this.connection.send(ack);
  437. return true;
  438. }
  439. // FIXME: check for a defined action
  440. this.connection.send(ack);
  441. // see http://xmpp.org/extensions/xep-0166.html#concepts-session
  442. switch (action) {
  443. case 'session-initiate':
  444. sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
  445. // configure session
  446. if (this.localStream) {
  447. sess.localStreams.push(this.localStream);
  448. }
  449. sess.media_constraints = this.media_constraints;
  450. sess.pc_constraints = this.pc_constraints;
  451. sess.ice_config = this.ice_config;
  452. sess.initiate($(iq).attr('from'), false);
  453. // FIXME: setRemoteDescription should only be done when this call is to be accepted
  454. sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
  455. this.sessions[sess.sid] = sess;
  456. this.jid2session[sess.peerjid] = sess;
  457. // the callback should either
  458. // .sendAnswer and .accept
  459. // or .sendTerminate -- not necessarily synchronus
  460. $(document).trigger('callincoming.jingle', [sess.sid]);
  461. break;
  462. case 'session-accept':
  463. sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
  464. sess.accept();
  465. $(document).trigger('callaccepted.jingle', [sess.sid]);
  466. break;
  467. case 'session-terminate':
  468. console.log('terminating...');
  469. sess.terminate();
  470. this.terminate(sess.sid);
  471. if ($(iq).find('>jingle>reason').length) {
  472. $(document).trigger('callterminated.jingle', [
  473. sess.sid,
  474. $(iq).find('>jingle>reason>:first')[0].tagName,
  475. $(iq).find('>jingle>reason>text').text()
  476. ]);
  477. } else {
  478. $(document).trigger('callterminated.jingle', [sess.sid]);
  479. }
  480. break;
  481. case 'transport-info':
  482. sess.addIceCandidate($(iq).find('>jingle>content'));
  483. break;
  484. case 'session-info':
  485. var affected;
  486. if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  487. $(document).trigger('ringing.jingle', [sess.sid]);
  488. } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  489. affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
  490. $(document).trigger('mute.jingle', [sess.sid, affected]);
  491. } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  492. affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
  493. $(document).trigger('unmute.jingle', [sess.sid, affected]);
  494. }
  495. break;
  496. case 'addsource': // FIXME: proprietary
  497. sess.addSource($(iq).find('>jingle>content'));
  498. break;
  499. case 'removesource': // FIXME: proprietary
  500. sess.removeSource($(iq).find('>jingle>content'));
  501. break;
  502. default:
  503. console.warn('jingle action not implemented', action);
  504. break;
  505. }
  506. return true;
  507. },
  508. initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
  509. var sess = new JingleSession(myjid || this.connection.jid,
  510. Math.random().toString(36).substr(2, 12), // random string
  511. this.connection);
  512. // configure session
  513. if (this.localStream) {
  514. sess.localStreams.push(this.localStream);
  515. }
  516. sess.media_constraints = this.media_constraints;
  517. sess.pc_constraints = this.pc_constraints;
  518. sess.ice_config = this.ice_config;
  519. sess.initiate(peerjid, true);
  520. this.sessions[sess.sid] = sess;
  521. this.jid2session[sess.peerjid] = sess;
  522. sess.sendOffer();
  523. return sess;
  524. },
  525. terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
  526. if (sid === null || sid === undefined) {
  527. for (sid in this.sessions) {
  528. if (this.sessions[sid].state != 'ended') {
  529. this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
  530. this.sessions[sid].terminate();
  531. }
  532. delete this.jid2session[this.sessions[sid].peerjid];
  533. delete this.sessions[sid];
  534. }
  535. } else if (this.sessions.hasOwnProperty(sid)) {
  536. if (this.sessions[sid].state != 'ended') {
  537. this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
  538. this.sessions[sid].terminate();
  539. }
  540. delete this.jid2session[this.sessions[sid].peerjid];
  541. delete this.sessions[sid];
  542. }
  543. },
  544. terminateByJid: function (jid) {
  545. if (this.jid2session.hasOwnProperty(jid)) {
  546. var sess = this.jid2session[jid];
  547. if (sess) {
  548. sess.terminate();
  549. console.log('peer went away silently', jid);
  550. delete this.sessions[sess.sid];
  551. delete this.jid2session[jid];
  552. $(document).trigger('callterminated.jingle', [sess.sid, 'gone']);
  553. }
  554. }
  555. },
  556. getStunAndTurnCredentials: function () {
  557. // get stun and turn configuration from server via xep-0215
  558. // uses time-limited credentials as described in
  559. // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
  560. //
  561. // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
  562. // for a prosody module which implements this
  563. //
  564. // currently, this doesn't work with updateIce and therefore credentials with a long
  565. // validity have to be fetched before creating the peerconnection
  566. // TODO: implement refresh via updateIce as described in
  567. // https://code.google.com/p/webrtc/issues/detail?id=1650
  568. var self = this;
  569. this.connection.sendIQ(
  570. $iq({type: 'get', to: this.connection.domain})
  571. .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
  572. function (res) {
  573. var iceservers = [];
  574. $(res).find('>services>service').each(function (idx, el) {
  575. el = $(el);
  576. var dict = {};
  577. switch (el.attr('type')) {
  578. case 'stun':
  579. dict.url = 'stun:' + el.attr('host');
  580. if (el.attr('port')) {
  581. dict.url += ':' + el.attr('port');
  582. }
  583. iceservers.push(dict);
  584. break;
  585. case 'turn':
  586. dict.url = 'turn:';
  587. if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
  588. if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
  589. dict.url += el.attr('username') + '@';
  590. } else {
  591. dict.username = el.attr('username'); // only works in M28
  592. }
  593. }
  594. dict.url += el.attr('host');
  595. if (el.attr('port') && el.attr('port') != '3478') {
  596. dict.url += ':' + el.attr('port');
  597. }
  598. if (el.attr('transport') && el.attr('transport') != 'udp') {
  599. dict.url += '?transport=' + el.attr('transport');
  600. }
  601. if (el.attr('password')) {
  602. dict.credential = el.attr('password');
  603. }
  604. iceservers.push(dict);
  605. break;
  606. }
  607. });
  608. self.ice_config.iceServers = iceservers;
  609. },
  610. function (err) {
  611. console.warn('getting turn credentials failed', err);
  612. console.warn('is mod_turncredentials or similar installed?');
  613. }
  614. );
  615. // implement push?
  616. }
  617. });
  618. /* jshint -W117 */
  619. // SDP STUFF
  620. function SDP(sdp) {
  621. this.media = sdp.split('\r\nm=');
  622. for (var i = 1; i < this.media.length; i++) {
  623. this.media[i] = 'm=' + this.media[i];
  624. if (i != this.media.length - 1) {
  625. this.media[i] += '\r\n';
  626. }
  627. }
  628. this.session = this.media.shift() + '\r\n';
  629. this.raw = this.session + this.media.join('');
  630. }
  631. // remove iSAC and CN from SDP
  632. SDP.prototype.mangle = function () {
  633. var i, j, mline, lines, rtpmap, newdesc;
  634. for (i = 0; i < this.media.length; i++) {
  635. lines = this.media[i].split('\r\n');
  636. lines.pop(); // remove empty last element
  637. mline = SDPUtil.parse_mline(lines.shift());
  638. if (mline.media != 'audio')
  639. continue;
  640. newdesc = '';
  641. mline.fmt.length = 0;
  642. for (j = 0; j < lines.length; j++) {
  643. if (lines[j].substr(0, 9) == 'a=rtpmap:') {
  644. rtpmap = SDPUtil.parse_rtpmap(lines[j]);
  645. if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
  646. continue;
  647. mline.fmt.push(rtpmap.id);
  648. newdesc += lines[j] + '\r\n';
  649. } else {
  650. newdesc += lines[j] + '\r\n';
  651. }
  652. }
  653. this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
  654. this.media[i] += newdesc;
  655. }
  656. this.raw = this.session + this.media.join('');
  657. };
  658. // remove lines matching prefix from session section
  659. SDP.prototype.removeSessionLines = function(prefix) {
  660. var self = this;
  661. var lines = SDPUtil.find_lines(this.session, prefix);
  662. lines.forEach(function(line) {
  663. self.session = self.session.replace(line + '\r\n', '');
  664. });
  665. this.raw = this.session + this.media.join('');
  666. return lines;
  667. }
  668. // remove lines matching prefix from a media section specified by mediaindex
  669. // TODO: non-numeric mediaindex could match mid
  670. SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
  671. var self = this;
  672. var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
  673. lines.forEach(function(line) {
  674. self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
  675. });
  676. this.raw = this.session + this.media.join('');
  677. return lines;
  678. }
  679. // add content's to a jingle element
  680. SDP.prototype.toJingle = function (elem, thecreator) {
  681. var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
  682. var self = this;
  683. // new bundle plan
  684. if (SDPUtil.find_line(this.session, 'a=group:')) {
  685. lines = SDPUtil.find_lines(this.session, 'a=group:');
  686. for (i = 0; i < lines.length; i++) {
  687. tmp = lines[i].split(' ');
  688. var semantics = tmp.shift().substr(8);
  689. elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
  690. for (j = 0; j < tmp.length; j++) {
  691. elem.c('content', {name: tmp[j]}).up();
  692. }
  693. elem.up();
  694. }
  695. }
  696. // old bundle plan, to be removed
  697. var bundle = [];
  698. if (SDPUtil.find_line(this.session, 'a=group:BUNDLE')) {
  699. bundle = SDPUtil.find_line(this.session, 'a=group:BUNDLE ').split(' ');
  700. bundle.shift();
  701. }
  702. for (i = 0; i < this.media.length; i++) {
  703. mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
  704. if (!(mline.media == 'audio' || mline.media == 'video')) {
  705. continue;
  706. }
  707. if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
  708. ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
  709. } else {
  710. ssrc = false;
  711. }
  712. elem.c('content', {creator: thecreator, name: mline.media});
  713. if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
  714. // prefer identifier from a=mid if present
  715. var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
  716. elem.attrs({ name: mid });
  717. // old BUNDLE plan, to be removed
  718. if (bundle.indexOf(mid) != -1) {
  719. elem.c('bundle', {xmlns: 'http://estos.de/ns/bundle'}).up();
  720. bundle.splice(bundle.indexOf(mid), 1);
  721. }
  722. }
  723. if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) {
  724. elem.c('description',
  725. {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
  726. media: mline.media });
  727. if (ssrc) {
  728. elem.attrs({ssrc: ssrc});
  729. }
  730. for (j = 0; j < mline.fmt.length; j++) {
  731. rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
  732. elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
  733. // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
  734. if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
  735. tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
  736. for (k = 0; k < tmp.length; k++) {
  737. elem.c('parameter', tmp[k]).up();
  738. }
  739. }
  740. this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
  741. elem.up();
  742. }
  743. if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
  744. elem.c('encryption', {required: 1});
  745. var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
  746. crypto.forEach(function(line) {
  747. elem.c('crypto', SDPUtil.parse_crypto(line)).up();
  748. });
  749. elem.up(); // end of encryption
  750. }
  751. if (ssrc) {
  752. // new style mapping
  753. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  754. // FIXME: group by ssrc and support multiple different ssrcs
  755. var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
  756. ssrclines.forEach(function(line) {
  757. idx = line.indexOf(' ');
  758. var linessrc = line.substr(0, idx).substr(7);
  759. if (linessrc != ssrc) {
  760. elem.up();
  761. ssrc = linessrc;
  762. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  763. }
  764. var kv = line.substr(idx + 1);
  765. elem.c('parameter');
  766. if (kv.indexOf(':') == -1) {
  767. elem.attrs({ name: kv });
  768. } else {
  769. elem.attrs({ name: kv.split(':', 2)[0] });
  770. elem.attrs({ value: kv.split(':', 2)[1] });
  771. }
  772. elem.up();
  773. });
  774. elem.up();
  775. // old proprietary mapping, to be removed at some point
  776. tmp = SDPUtil.parse_ssrc(this.media[i]);
  777. tmp.xmlns = 'http://estos.de/ns/ssrc';
  778. tmp.ssrc = ssrc;
  779. elem.c('ssrc', tmp).up(); // ssrc is part of description
  780. }
  781. if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
  782. elem.c('rtcp-mux').up();
  783. }
  784. // XEP-0293 -- map a=rtcp-fb:*
  785. this.RtcpFbToJingle(i, elem, '*');
  786. // XEP-0294
  787. if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
  788. lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
  789. for (j = 0; j < lines.length; j++) {
  790. tmp = SDPUtil.parse_extmap(lines[j]);
  791. elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
  792. uri: tmp.uri,
  793. id: tmp.value });
  794. if (tmp.hasOwnProperty('direction')) {
  795. switch (tmp.direction) {
  796. case 'sendonly':
  797. elem.attrs({senders: 'responder'});
  798. break;
  799. case 'recvonly':
  800. elem.attrs({senders: 'initiator'});
  801. break;
  802. case 'sendrecv':
  803. elem.attrs({senders: 'both'});
  804. break;
  805. case 'inactive':
  806. elem.attrs({senders: 'none'});
  807. break;
  808. }
  809. }
  810. // TODO: handle params
  811. elem.up();
  812. }
  813. }
  814. elem.up(); // end of description
  815. }
  816. // map ice-ufrag/pwd, dtls fingerprint, candidates
  817. this.TransportToJingle(i, elem);
  818. if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
  819. elem.attrs({senders: 'both'});
  820. } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
  821. elem.attrs({senders: 'initiator'});
  822. } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
  823. elem.attrs({senders: 'responder'});
  824. } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
  825. elem.attrs({senders: 'none'});
  826. }
  827. if (mline.port == '0') {
  828. // estos hack to reject an m-line
  829. elem.attrs({senders: 'rejected'});
  830. }
  831. elem.up(); // end of content
  832. }
  833. elem.up();
  834. return elem;
  835. };
  836. SDP.prototype.TransportToJingle = function (mediaindex, elem) {
  837. var i = mediaindex;
  838. var tmp;
  839. var self = this;
  840. elem.c('transport');
  841. // XEP-0320
  842. var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
  843. fingerprints.forEach(function(line) {
  844. tmp = SDPUtil.parse_fingerprint(line);
  845. tmp.xmlns = 'urn:xmpp:tmp:jingle:apps:dtls:0';
  846. // tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; -- FIXME: update receivers first
  847. elem.c('fingerprint').t(tmp.fingerprint);
  848. delete tmp.fingerprint;
  849. line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
  850. if (line) {
  851. tmp.setup = line.substr(8);
  852. }
  853. elem.attrs(tmp);
  854. elem.up(); // end of fingerprint
  855. });
  856. tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
  857. if (tmp) {
  858. tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  859. elem.attrs(tmp);
  860. // XEP-0176
  861. if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
  862. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
  863. lines.forEach(function (line) {
  864. elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
  865. });
  866. }
  867. }
  868. elem.up(); // end of transport
  869. }
  870. SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
  871. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
  872. lines.forEach(function (line) {
  873. var tmp = SDPUtil.parse_rtcpfb(line);
  874. if (tmp.type == 'trr-int') {
  875. elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
  876. elem.up();
  877. } else {
  878. elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
  879. if (tmp.params.length > 0) {
  880. elem.attrs({'subtype': tmp.params[0]});
  881. }
  882. elem.up();
  883. }
  884. });
  885. };
  886. SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
  887. var media = '';
  888. var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  889. if (tmp.length) {
  890. media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
  891. if (tmp.attr('value')) {
  892. media += tmp.attr('value');
  893. } else {
  894. media += '0';
  895. }
  896. media += '\r\n';
  897. }
  898. tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  899. tmp.each(function () {
  900. media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
  901. if ($(this).attr('subtype')) {
  902. media += ' ' + $(this).attr('subtype');
  903. }
  904. media += '\r\n';
  905. });
  906. return media;
  907. };
  908. // construct an SDP from a jingle stanza
  909. SDP.prototype.fromJingle = function (jingle) {
  910. var self = this;
  911. this.raw = 'v=0\r\n' +
  912. 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
  913. 's=-\r\n' +
  914. 't=0 0\r\n';
  915. // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
  916. if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
  917. $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
  918. var contents = $(group).find('>content').map(function (idx, content) {
  919. return content.getAttribute('name');
  920. }).get();
  921. if (contents.length > 0) {
  922. self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
  923. }
  924. });
  925. } else if ($(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').length) {
  926. // temporary namespace, not to be used. to be removed soon.
  927. $(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').each(function (idx, group) {
  928. var contents = $(group).find('>content').map(function (idx, content) {
  929. return content.getAttribute('name');
  930. }).get();
  931. if (group.getAttribute('type') !== null && contents.length > 0) {
  932. self.raw += 'a=group:' + group.getAttribute('type') + ' ' + contents.join(' ') + '\r\n';
  933. }
  934. });
  935. } else {
  936. // for backward compability, to be removed soon
  937. // assume all contents are in the same bundle group, can be improved upon later
  938. var bundle = $(jingle).find('>content').filter(function (idx, content) {
  939. //elem.c('bundle', {xmlns:'http://estos.de/ns/bundle'});
  940. return $(content).find('>bundle').length > 0;
  941. }).map(function (idx, content) {
  942. return content.getAttribute('name');
  943. }).get();
  944. if (bundle.length) {
  945. this.raw += 'a=group:BUNDLE ' + bundle.join(' ') + '\r\n';
  946. }
  947. }
  948. this.session = this.raw;
  949. jingle.find('>content').each(function () {
  950. var m = self.jingle2media($(this));
  951. self.media.push(m);
  952. });
  953. // reconstruct msid-semantic -- apparently not necessary
  954. /*
  955. var msid = SDPUtil.parse_ssrc(this.raw);
  956. if (msid.hasOwnProperty('mslabel')) {
  957. this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
  958. }
  959. */
  960. this.raw = this.session + this.media.join('');
  961. };
  962. // translate a jingle content element into an an SDP media part
  963. SDP.prototype.jingle2media = function (content) {
  964. var media = '',
  965. desc = content.find('description'),
  966. ssrc = desc.attr('ssrc'),
  967. self = this,
  968. tmp;
  969. tmp = { media: desc.attr('media') };
  970. tmp.port = '1';
  971. if (content.attr('senders') == 'rejected') {
  972. // estos hack to reject an m-line.
  973. tmp.port = '0';
  974. }
  975. if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
  976. tmp.proto = 'RTP/SAVPF';
  977. } else {
  978. tmp.proto = 'RTP/AVPF';
  979. }
  980. tmp.fmt = desc.find('payload-type').map(function () { return this.getAttribute('id'); }).get();
  981. media += SDPUtil.build_mline(tmp) + '\r\n';
  982. media += 'c=IN IP4 0.0.0.0\r\n';
  983. media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
  984. tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
  985. if (tmp.length) {
  986. if (tmp.attr('ufrag')) {
  987. media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
  988. }
  989. if (tmp.attr('pwd')) {
  990. media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
  991. }
  992. tmp.find('>fingerprint').each(function () {
  993. // FIXME: check namespace at some point
  994. media += 'a=fingerprint:' + this.getAttribute('hash');
  995. media += ' ' + $(this).text();
  996. media += '\r\n';
  997. if (this.getAttribute('setup')) {
  998. media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
  999. }
  1000. });
  1001. }
  1002. switch (content.attr('senders')) {
  1003. case 'initiator':
  1004. media += 'a=sendonly\r\n';
  1005. break;
  1006. case 'responder':
  1007. media += 'a=recvonly\r\n';
  1008. break;
  1009. case 'none':
  1010. media += 'a=inactive\r\n';
  1011. break;
  1012. case 'both':
  1013. media += 'a=sendrecv\r\n';
  1014. break;
  1015. }
  1016. media += 'a=mid:' + content.attr('name') + '\r\n';
  1017. // <description><rtcp-mux/></description>
  1018. // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
  1019. // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
  1020. if (desc.find('rtcp-mux').length) {
  1021. media += 'a=rtcp-mux\r\n';
  1022. }
  1023. if (desc.find('encryption').length) {
  1024. desc.find('encryption>crypto').each(function () {
  1025. media += 'a=crypto:' + this.getAttribute('tag');
  1026. media += ' ' + this.getAttribute('crypto-suite');
  1027. media += ' ' + this.getAttribute('key-params');
  1028. if (this.getAttribute('session-params')) {
  1029. media += ' ' + this.getAttribute('session-params');
  1030. }
  1031. media += '\r\n';
  1032. });
  1033. }
  1034. desc.find('payload-type').each(function () {
  1035. media += SDPUtil.build_rtpmap(this) + '\r\n';
  1036. if ($(this).find('>parameter').length) {
  1037. media += 'a=fmtp:' + this.getAttribute('id') + ' ';
  1038. media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join(';');
  1039. media += '\r\n';
  1040. }
  1041. // xep-0293
  1042. media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
  1043. });
  1044. // xep-0293
  1045. media += self.RtcpFbFromJingle(desc, '*');
  1046. // xep-0294
  1047. tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
  1048. tmp.each(function () {
  1049. media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
  1050. });
  1051. content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
  1052. media += SDPUtil.candidateFromJingle(this);
  1053. });
  1054. tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
  1055. tmp.each(function () {
  1056. var ssrc = this.getAttribute('ssrc');
  1057. $(this).find('>parameter').each(function () {
  1058. media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
  1059. if (this.getAttribute('value') && this.getAttribute('value').length)
  1060. media += ':' + this.getAttribute('value');
  1061. media += '\r\n';
  1062. });
  1063. });
  1064. if (tmp.length === 0) {
  1065. // fallback to proprietary mapping of a=ssrc lines
  1066. tmp = content.find('description>ssrc[xmlns="http://estos.de/ns/ssrc"]');
  1067. if (tmp.length) {
  1068. media += 'a=ssrc:' + ssrc + ' cname:' + tmp.attr('cname') + '\r\n';
  1069. media += 'a=ssrc:' + ssrc + ' msid:' + tmp.attr('msid') + '\r\n';
  1070. media += 'a=ssrc:' + ssrc + ' mslabel:' + tmp.attr('mslabel') + '\r\n';
  1071. media += 'a=ssrc:' + ssrc + ' label:' + tmp.attr('label') + '\r\n';
  1072. }
  1073. }
  1074. return media;
  1075. };
  1076. SDPUtil = {
  1077. iceparams: function (mediadesc, sessiondesc) {
  1078. var data = null;
  1079. if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
  1080. SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
  1081. data = {
  1082. ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
  1083. pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
  1084. };
  1085. }
  1086. return data;
  1087. },
  1088. parse_iceufrag: function (line) {
  1089. return line.substring(12);
  1090. },
  1091. build_iceufrag: function (frag) {
  1092. return 'a=ice-ufrag:' + frag;
  1093. },
  1094. parse_icepwd: function (line) {
  1095. return line.substring(10);
  1096. },
  1097. build_icepwd: function (pwd) {
  1098. return 'a=ice-pwd:' + pwd;
  1099. },
  1100. parse_mid: function (line) {
  1101. return line.substring(6);
  1102. },
  1103. parse_mline: function (line) {
  1104. var parts = line.substring(2).split(' '),
  1105. data = {};
  1106. data.media = parts.shift();
  1107. data.port = parts.shift();
  1108. data.proto = parts.shift();
  1109. if (parts[parts.length - 1] === '') { // trailing whitespace
  1110. parts.pop();
  1111. }
  1112. data.fmt = parts;
  1113. return data;
  1114. },
  1115. build_mline: function (mline) {
  1116. return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
  1117. },
  1118. parse_rtpmap: function (line) {
  1119. var parts = line.substring(9).split(' '),
  1120. data = {};
  1121. data.id = parts.shift();
  1122. parts = parts[0].split('/');
  1123. data.name = parts.shift();
  1124. data.clockrate = parts.shift();
  1125. data.channels = parts.length ? parts.shift() : '1';
  1126. return data;
  1127. },
  1128. build_rtpmap: function (el) {
  1129. var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
  1130. if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
  1131. line += '/' + el.getAttribute('channels');
  1132. }
  1133. return line;
  1134. },
  1135. parse_crypto: function (line) {
  1136. var parts = line.substring(9).split(' '),
  1137. data = {};
  1138. data.tag = parts.shift();
  1139. data['crypto-suite'] = parts.shift();
  1140. data['key-params'] = parts.shift();
  1141. if (parts.length) {
  1142. data['session-params'] = parts.join(' ');
  1143. }
  1144. return data;
  1145. },
  1146. parse_fingerprint: function (line) { // RFC 4572
  1147. var parts = line.substring(14).split(' '),
  1148. data = {};
  1149. data.hash = parts.shift();
  1150. data.fingerprint = parts.shift();
  1151. // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
  1152. return data;
  1153. },
  1154. parse_fmtp: function (line) {
  1155. var parts = line.split(' '),
  1156. i, key, value,
  1157. data = [];
  1158. parts.shift();
  1159. parts = parts.join(' ').split(';');
  1160. for (i = 0; i < parts.length; i++) {
  1161. key = parts[i].split('=')[0];
  1162. while (key.length && key[0] == ' ') {
  1163. key = key.substring(1);
  1164. }
  1165. value = parts[i].split('=')[1];
  1166. if (key && value) {
  1167. data.push({name: key, value: value});
  1168. } else if (key) {
  1169. // rfc 4733 (DTMF) style stuff
  1170. data.push({name: '', value: key});
  1171. }
  1172. }
  1173. return data;
  1174. },
  1175. parse_icecandidate: function (line) {
  1176. var candidate = {},
  1177. elems = line.split(' ');
  1178. candidate.foundation = elems[0].substring(12);
  1179. candidate.component = elems[1];
  1180. candidate.protocol = elems[2].toLowerCase();
  1181. candidate.priority = elems[3];
  1182. candidate.ip = elems[4];
  1183. candidate.port = elems[5];
  1184. // elems[6] => "typ"
  1185. candidate.type = elems[7];
  1186. candidate.generation = 0; // default value, may be overwritten below
  1187. for (var i = 8; i < elems.length; i += 2) {
  1188. switch (elems[i]) {
  1189. case 'raddr':
  1190. candidate['rel-addr'] = elems[i + 1];
  1191. break;
  1192. case 'rport':
  1193. candidate['rel-port'] = elems[i + 1];
  1194. break;
  1195. case 'generation':
  1196. candidate.generation = elems[i + 1];
  1197. break;
  1198. default: // TODO
  1199. console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  1200. }
  1201. }
  1202. candidate.network = '1';
  1203. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  1204. return candidate;
  1205. },
  1206. build_icecandidate: function (cand) {
  1207. var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
  1208. line += ' ';
  1209. switch (cand.type) {
  1210. case 'srflx':
  1211. case 'prflx':
  1212. case 'relay':
  1213. if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
  1214. line += 'raddr';
  1215. line += ' ';
  1216. line += cand['rel-addr'];
  1217. line += ' ';
  1218. line += 'rport';
  1219. line += ' ';
  1220. line += cand['rel-port'];
  1221. line += ' ';
  1222. }
  1223. break;
  1224. }
  1225. line += 'generation';
  1226. line += ' ';
  1227. line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
  1228. return line;
  1229. },
  1230. parse_ssrc: function (desc) {
  1231. // proprietary mapping of a=ssrc lines
  1232. // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
  1233. // and parse according to that
  1234. var lines = desc.split('\r\n'),
  1235. data = {};
  1236. for (var i = 0; i < lines.length; i++) {
  1237. if (lines[i].substring(0, 7) == 'a=ssrc:') {
  1238. var idx = lines[i].indexOf(' ');
  1239. data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
  1240. }
  1241. }
  1242. return data;
  1243. },
  1244. parse_rtcpfb: function (line) {
  1245. var parts = line.substr(10).split(' ');
  1246. var data = {};
  1247. data.pt = parts.shift();
  1248. data.type = parts.shift();
  1249. data.params = parts;
  1250. return data;
  1251. },
  1252. parse_extmap: function (line) {
  1253. var parts = line.substr(9).split(' ');
  1254. var data = {};
  1255. data.value = parts.shift();
  1256. if (data.value.indexOf('/') != -1) {
  1257. data.direction = data.value.substr(data.value.indexOf('/') + 1);
  1258. data.value = data.value.substr(0, data.value.indexOf('/'));
  1259. } else {
  1260. data.direction = 'both';
  1261. }
  1262. data.uri = parts.shift();
  1263. data.params = parts;
  1264. return data;
  1265. },
  1266. find_line: function (haystack, needle, sessionpart) {
  1267. var lines = haystack.split('\r\n');
  1268. for (var i = 0; i < lines.length; i++) {
  1269. if (lines[i].substring(0, needle.length) == needle) {
  1270. return lines[i];
  1271. }
  1272. }
  1273. if (!sessionpart) {
  1274. return false;
  1275. }
  1276. // search session part
  1277. lines = sessionpart.split('\r\n');
  1278. for (var j = 0; j < lines.length; j++) {
  1279. if (lines[j].substring(0, needle.length) == needle) {
  1280. return lines[j];
  1281. }
  1282. }
  1283. return false;
  1284. },
  1285. find_lines: function (haystack, needle, sessionpart) {
  1286. var lines = haystack.split('\r\n'),
  1287. needles = [];
  1288. for (var i = 0; i < lines.length; i++) {
  1289. if (lines[i].substring(0, needle.length) == needle)
  1290. needles.push(lines[i]);
  1291. }
  1292. if (needles.length || !sessionpart) {
  1293. return needles;
  1294. }
  1295. // search session part
  1296. lines = sessionpart.split('\r\n');
  1297. for (var j = 0; j < lines.length; j++) {
  1298. if (lines[j].substring(0, needle.length) == needle) {
  1299. needles.push(lines[j]);
  1300. }
  1301. }
  1302. return needles;
  1303. },
  1304. candidateToJingle: function (line) {
  1305. // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
  1306. // <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
  1307. if (line.substring(0, 12) != 'a=candidate:') {
  1308. console.log('parseCandidate called with a line that is not a candidate line');
  1309. console.log(line);
  1310. return null;
  1311. }
  1312. if (line.substring(line.length - 2) == '\r\n') // chomp it
  1313. line = line.substring(0, line.length - 2);
  1314. var candidate = {},
  1315. elems = line.split(' '),
  1316. i;
  1317. if (elems[6] != 'typ') {
  1318. console.log('did not find typ in the right place');
  1319. console.log(line);
  1320. return null;
  1321. }
  1322. candidate.foundation = elems[0].substring(12);
  1323. candidate.component = elems[1];
  1324. candidate.protocol = elems[2].toLowerCase();
  1325. candidate.priority = elems[3];
  1326. candidate.ip = elems[4];
  1327. candidate.port = elems[5];
  1328. // elems[6] => "typ"
  1329. candidate.type = elems[7];
  1330. for (i = 8; i < elems.length; i += 2) {
  1331. switch (elems[i]) {
  1332. case 'raddr':
  1333. candidate['rel-addr'] = elems[i + 1];
  1334. break;
  1335. case 'rport':
  1336. candidate['rel-port'] = elems[i + 1];
  1337. break;
  1338. case 'generation':
  1339. candidate.generation = elems[i + 1];
  1340. break;
  1341. default: // TODO
  1342. console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  1343. }
  1344. }
  1345. candidate.network = '1';
  1346. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  1347. return candidate;
  1348. },
  1349. candidateFromJingle: function (cand) {
  1350. var line = 'a=candidate:';
  1351. line += cand.getAttribute('foundation');
  1352. line += ' ';
  1353. line += cand.getAttribute('component');
  1354. line += ' ';
  1355. line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
  1356. line += ' ';
  1357. line += cand.getAttribute('priority');
  1358. line += ' ';
  1359. line += cand.getAttribute('ip');
  1360. line += ' ';
  1361. line += cand.getAttribute('port');
  1362. line += ' ';
  1363. line += 'typ';
  1364. line += ' ' + cand.getAttribute('type');
  1365. line += ' ';
  1366. switch (cand.getAttribute('type')) {
  1367. case 'srflx':
  1368. case 'prflx':
  1369. case 'relay':
  1370. if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
  1371. line += 'raddr';
  1372. line += ' ';
  1373. line += cand.getAttribute('rel-addr');
  1374. line += ' ';
  1375. line += 'rport';
  1376. line += ' ';
  1377. line += cand.getAttribute('rel-port');
  1378. line += ' ';
  1379. }
  1380. break;
  1381. }
  1382. line += 'generation';
  1383. line += ' ';
  1384. line += cand.getAttribute('generation') || '0';
  1385. return line + '\r\n';
  1386. }
  1387. };
  1388. /* jshint -W117 */
  1389. // Jingle stuff
  1390. function JingleSession(me, sid, connection) {
  1391. this.me = me;
  1392. this.sid = sid;
  1393. this.connection = connection;
  1394. this.initiator = null;
  1395. this.responder = null;
  1396. this.isInitiator = null;
  1397. this.peerjid = null;
  1398. this.state = null;
  1399. this.peerconnection = null;
  1400. this.remoteStream = null;
  1401. this.localSDP = null;
  1402. this.remoteSDP = null;
  1403. this.localStreams = [];
  1404. this.relayedStreams = [];
  1405. this.remoteStreams = [];
  1406. this.startTime = null;
  1407. this.stopTime = null;
  1408. this.media_constraints = null;
  1409. this.pc_constraints = null;
  1410. this.ice_config = {};
  1411. this.drip_container = [];
  1412. this.usetrickle = true;
  1413. this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
  1414. this.usedrip = false; // dripping is sending trickle candidates not one-by-one
  1415. this.hadstuncandidate = false;
  1416. this.hadturncandidate = false;
  1417. this.lasticecandidate = false;
  1418. this.statsinterval = null;
  1419. this.reason = null;
  1420. this.addssrc = [];
  1421. this.removessrc = [];
  1422. this.pendingop = null;
  1423. this.wait = true;
  1424. }
  1425. JingleSession.prototype.initiate = function (peerjid, isInitiator) {
  1426. var self = this;
  1427. if (this.state !== null) {
  1428. console.error('attempt to initiate on session ' + this.sid +
  1429. 'in state ' + this.state);
  1430. return;
  1431. }
  1432. this.isInitiator = isInitiator;
  1433. this.state = 'pending';
  1434. this.initiator = isInitiator ? this.me : peerjid;
  1435. this.responder = !isInitiator ? this.me : peerjid;
  1436. this.peerjid = peerjid;
  1437. //console.log('create PeerConnection ' + JSON.stringify(this.ice_config));
  1438. try {
  1439. this.peerconnection = new RTCPeerconnection(this.ice_config,
  1440. this.pc_constraints);
  1441. } catch (e) {
  1442. console.error('Failed to create PeerConnection, exception: ',
  1443. e.message);
  1444. console.error(e);
  1445. return;
  1446. }
  1447. this.hadstuncandidate = false;
  1448. this.hadturncandidate = false;
  1449. this.lasticecandidate = false;
  1450. this.peerconnection.onicecandidate = function (event) {
  1451. self.sendIceCandidate(event.candidate);
  1452. };
  1453. this.peerconnection.onaddstream = function (event) {
  1454. self.remoteStream = event.stream;
  1455. self.remoteStreams.push(event.stream);
  1456. $(document).trigger('remotestreamadded.jingle', [event, self.sid]);
  1457. };
  1458. this.peerconnection.onremovestream = function (event) {
  1459. self.remoteStream = null;
  1460. // FIXME: remove from this.remoteStreams
  1461. $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
  1462. };
  1463. this.peerconnection.onsignalingstatechange = function (event) {
  1464. if (!(self && self.peerconnection)) return;
  1465. };
  1466. this.peerconnection.oniceconnectionstatechange = function (event) {
  1467. if (!(self && self.peerconnection)) return;
  1468. switch (self.peerconnection.iceConnectionState) {
  1469. case 'connected':
  1470. this.startTime = new Date();
  1471. break;
  1472. case 'disconnected':
  1473. this.stopTime = new Date();
  1474. break;
  1475. }
  1476. $(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
  1477. };
  1478. // add any local and relayed stream
  1479. this.localStreams.forEach(function(stream) {
  1480. self.peerconnection.addStream(stream);
  1481. });
  1482. this.relayedStreams.forEach(function(stream) {
  1483. self.peerconnection.addStream(stream);
  1484. });
  1485. };
  1486. JingleSession.prototype.accept = function () {
  1487. var self = this;
  1488. this.state = 'active';
  1489. var pranswer = this.peerconnection.localDescription;
  1490. if (!pranswer || pranswer.type != 'pranswer') {
  1491. return;
  1492. }
  1493. console.log('going from pranswer to answer');
  1494. if (this.usetrickle) {
  1495. // remove candidates already sent from session-accept
  1496. var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
  1497. for (var i = 0; i < lines.length; i++) {
  1498. pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
  1499. }
  1500. }
  1501. while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
  1502. // FIXME: change any inactive to sendrecv or whatever they were originally
  1503. pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
  1504. }
  1505. var prsdp = new SDP(pranswer.sdp);
  1506. var accept = $iq({to: this.peerjid,
  1507. type: 'set'})
  1508. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1509. action: 'session-accept',
  1510. initiator: this.initiator,
  1511. responder: this.responder,
  1512. sid: this.sid });
  1513. prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder');
  1514. this.connection.sendIQ(accept,
  1515. function () {
  1516. var ack = {};
  1517. ack.source = 'answer';
  1518. $(document).trigger('ack.jingle', [self.sid, ack]);
  1519. },
  1520. function (stanza) {
  1521. var error = ($(stanza).find('error').length) ? {
  1522. code: $(stanza).find('error').attr('code'),
  1523. reason: $(stanza).find('error :first')[0].tagName,
  1524. }:{};
  1525. error.source = 'answer';
  1526. $(document).trigger('error.jingle', [self.sid, error]);
  1527. },
  1528. 10000);
  1529. var sdp = this.peerconnection.localDescription.sdp;
  1530. while (SDPUtil.find_line(sdp, 'a=inactive')) {
  1531. // FIXME: change any inactive to sendrecv or whatever they were originally
  1532. sdp = sdp.replace('a=inactive', 'a=sendrecv');
  1533. }
  1534. this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
  1535. function () {
  1536. //console.log('setLocalDescription success');
  1537. $(document).trigger('setLocalDescription.jingle', [self.sid]);
  1538. },
  1539. function (e) {
  1540. console.error('setLocalDescription failed', e);
  1541. }
  1542. );
  1543. };
  1544. JingleSession.prototype.terminate = function (reason) {
  1545. this.state = 'ended';
  1546. this.reason = reason;
  1547. this.peerconnection.close();
  1548. if (this.statsinterval !== null) {
  1549. window.clearInterval(this.statsinterval);
  1550. this.statsinterval = null;
  1551. }
  1552. };
  1553. JingleSession.prototype.active = function () {
  1554. return this.state == 'active';
  1555. };
  1556. JingleSession.prototype.sendIceCandidate = function (candidate) {
  1557. var self = this;
  1558. if (candidate && !this.lasticecandidate) {
  1559. var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
  1560. var jcand = SDPUtil.candidateToJingle(candidate.candidate);
  1561. if (!(ice && jcand)) {
  1562. console.error('failed to get ice && jcand');
  1563. return;
  1564. }
  1565. ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  1566. if (jcand.type === 'srflx') {
  1567. this.hadstuncandidate = true;
  1568. } else if (jcand.type === 'relay') {
  1569. this.hadturncandidate = true;
  1570. }
  1571. if (this.usetrickle) {
  1572. if (this.usedrip) {
  1573. if (this.drip_container.length === 0) {
  1574. // start 10ms callout
  1575. window.setTimeout(function () {
  1576. if (self.drip_container.length === 0) return;
  1577. var allcands = self.drip_container;
  1578. self.drip_container = [];
  1579. var cand = $iq({to: self.peerjid, type: 'set'})
  1580. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1581. action: 'transport-info',
  1582. initiator: self.initiator,
  1583. sid: self.sid});
  1584. for (var mid = 0; mid < self.localSDP.media.length; mid++) {
  1585. var cands = allcands.filter(function (el) { return el.sdpMLineIndex == mid; });
  1586. if (cands.length > 0) {
  1587. var ice = SDPUtil.iceparams(self.localSDP.media[mid], self.localSDP.session);
  1588. ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  1589. cand.c('content', {creator: self.initiator == self.me ? 'initiator' : 'responder',
  1590. name: cands[0].sdpMid
  1591. }).c('transport', ice);
  1592. for (var i = 0; i < cands.length; i++) {
  1593. cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
  1594. }
  1595. // add fingerprint
  1596. if (SDPUtil.find_line(self.localSDP.media[mid], 'a=fingerprint:', self.localSDP.session)) {
  1597. var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(self.localSDP.media[mid], 'a=fingerprint:', self.localSDP.session));
  1598. tmp.required = true;
  1599. cand.c('fingerprint').t(tmp.fingerprint);
  1600. delete tmp.fingerprint;
  1601. cand.attrs(tmp);
  1602. cand.up();
  1603. }
  1604. cand.up(); // transport
  1605. cand.up(); // content
  1606. }
  1607. }
  1608. // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
  1609. //console.log('was this the last candidate', self.lasticecandidate);
  1610. self.connection.sendIQ(cand,
  1611. function () {
  1612. var ack = {};
  1613. ack.source = 'transportinfo';
  1614. $(document).trigger('ack.jingle', [self.sid, ack]);
  1615. },
  1616. function (stanza) {
  1617. var error = ($(stanza).find('error').length) ? {
  1618. code: $(stanza).find('error').attr('code'),
  1619. reason: $(stanza).find('error :first')[0].tagName,
  1620. }:{};
  1621. error.source = 'transportinfo';
  1622. $(document).trigger('error.jingle', [self.sid, error]);
  1623. },
  1624. 10000);
  1625. }, 10);
  1626. }
  1627. this.drip_container.push(event.candidate);
  1628. return;
  1629. }
  1630. // map to transport-info
  1631. var cand = $iq({to: this.peerjid, type: 'set'})
  1632. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1633. action: 'transport-info',
  1634. initiator: this.initiator,
  1635. sid: this.sid})
  1636. .c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
  1637. name: candidate.sdpMid
  1638. })
  1639. .c('transport', ice)
  1640. .c('candidate', jcand);
  1641. cand.up();
  1642. // add fingerprint
  1643. if (SDPUtil.find_line(this.localSDP.media[candidate.sdpMLineIndex], 'a=fingerprint:', this.localSDP.session)) {
  1644. var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[candidate.sdpMLineIndex], 'a=fingerprint:', this.localSDP.session));
  1645. tmp.required = true;
  1646. cand.c('fingerprint').t(tmp.fingerprint);
  1647. delete tmp.fingerprint;
  1648. cand.attrs(tmp);
  1649. cand.up();
  1650. }
  1651. this.connection.sendIQ(cand,
  1652. function () {
  1653. var ack = {};
  1654. ack.source = 'transportinfo';
  1655. $(document).trigger('ack.jingle', [self.sid, ack]);
  1656. },
  1657. function (stanza) {
  1658. console.error('transport info error');
  1659. var error = ($(stanza).find('error').length) ? {
  1660. code: $(stanza).find('error').attr('code'),
  1661. reason: $(stanza).find('error :first')[0].tagName,
  1662. }:{};
  1663. error.source = 'transportinfo';
  1664. $(document).trigger('error.jingle', [self.sid, error]);
  1665. },
  1666. 10000);
  1667. }
  1668. } else {
  1669. //console.log('sendIceCandidate: last candidate.');
  1670. if (!this.usetrickle) {
  1671. //console.log('should send full offer now...');
  1672. var init = $iq({to: this.peerjid,
  1673. type: 'set'})
  1674. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1675. action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
  1676. initiator: this.initiator,
  1677. sid: this.sid});
  1678. this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
  1679. this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder');
  1680. this.connection.sendIQ(init,
  1681. function () {
  1682. //console.log('session initiate ack');
  1683. var ack = {};
  1684. ack.source = 'offer';
  1685. $(document).trigger('ack.jingle', [self.sid, ack]);
  1686. },
  1687. function (stanza) {
  1688. self.state = 'error';
  1689. self.peerconnection.close();
  1690. var error = ($(stanza).find('error').length) ? {
  1691. code: $(stanza).find('error').attr('code'),
  1692. reason: $(stanza).find('error :first')[0].tagName,
  1693. }:{};
  1694. error.source = 'offer';
  1695. $(document).trigger('error.jingle', [self.sid, error]);
  1696. },
  1697. 10000);
  1698. }
  1699. this.lasticecandidate = true;
  1700. console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
  1701. console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
  1702. if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
  1703. $(document).trigger('nostuncandidates.jingle', [this.sid]);
  1704. }
  1705. }
  1706. };
  1707. JingleSession.prototype.sendOffer = function () {
  1708. //console.log('sendOffer...');
  1709. var self = this;
  1710. this.peerconnection.createOffer(function (sdp) {
  1711. self.createdOffer(sdp);
  1712. },
  1713. function (e) {
  1714. console.error('createOffer failed', e);
  1715. },
  1716. this.media_constraints
  1717. );
  1718. };
  1719. JingleSession.prototype.createdOffer = function (sdp) {
  1720. //console.log('createdOffer', sdp);
  1721. var self = this;
  1722. this.localSDP = new SDP(sdp.sdp);
  1723. //this.localSDP.mangle();
  1724. if (this.usetrickle) {
  1725. var init = $iq({to: this.peerjid,
  1726. type: 'set'})
  1727. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1728. action: 'session-initiate',
  1729. initiator: this.initiator,
  1730. sid: this.sid});
  1731. this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder');
  1732. this.connection.sendIQ(init,
  1733. function () {
  1734. var ack = {};
  1735. ack.source = 'offer';
  1736. $(document).trigger('ack.jingle', [self.sid, ack]);
  1737. },
  1738. function (stanza) {
  1739. self.state = 'error';
  1740. self.peerconnection.close();
  1741. var error = ($(stanza).find('error').length) ? {
  1742. code: $(stanza).find('error').attr('code'),
  1743. reason: $(stanza).find('error :first')[0].tagName,
  1744. }:{};
  1745. error.source = 'offer';
  1746. $(document).trigger('error.jingle', [self.sid, error]);
  1747. },
  1748. 10000);
  1749. }
  1750. sdp.sdp = this.localSDP.raw;
  1751. this.peerconnection.setLocalDescription(sdp,
  1752. function () {
  1753. $(document).trigger('setLocalDescription.jingle', [self.sid]);
  1754. //console.log('setLocalDescription success');
  1755. },
  1756. function (e) {
  1757. console.error('setLocalDescription failed', e);
  1758. }
  1759. );
  1760. var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
  1761. for (var i = 0; i < cands.length; i++) {
  1762. var cand = SDPUtil.parse_icecandidate(cands[i]);
  1763. if (cand.type == 'srflx') {
  1764. this.hadstuncandidate = true;
  1765. } else if (cand.type == 'relay') {
  1766. this.hadturncandidate = true;
  1767. }
  1768. }
  1769. };
  1770. JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
  1771. //console.log('setting remote description... ', desctype);
  1772. this.remoteSDP = new SDP('');
  1773. this.remoteSDP.fromJingle(elem);
  1774. if (this.peerconnection.remoteDescription !== null) {
  1775. console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
  1776. if (this.peerconnection.remoteDescription.type == 'pranswer') {
  1777. var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
  1778. for (var i = 0; i < pranswer.media.length; i++) {
  1779. // make sure we have ice ufrag and pwd
  1780. if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
  1781. if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
  1782. this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
  1783. } else {
  1784. console.warn('no ice ufrag?');
  1785. }
  1786. if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
  1787. this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
  1788. } else {
  1789. console.warn('no ice pwd?');
  1790. }
  1791. }
  1792. // copy over candidates
  1793. var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
  1794. for (var j = 0; j < lines.length; j++) {
  1795. this.remoteSDP.media[i] += lines[j] + '\r\n';
  1796. }
  1797. }
  1798. this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
  1799. }
  1800. }
  1801. var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
  1802. this.peerconnection.setRemoteDescription(remotedesc,
  1803. function () {
  1804. //console.log('setRemoteDescription success');
  1805. },
  1806. function (e) {
  1807. console.error('setRemoteDescription error', e);
  1808. }
  1809. );
  1810. };
  1811. JingleSession.prototype.addIceCandidate = function (elem) {
  1812. var self = this;
  1813. if (this.peerconnection.signalingState == 'closed') {
  1814. return;
  1815. }
  1816. if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
  1817. console.log('trickle ice candidate arriving before session accept...');
  1818. // create a PRANSWER for setRemoteDescription
  1819. if (!this.remoteSDP) {
  1820. var cobbled = 'v=0\r\n' +
  1821. 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
  1822. 's=-\r\n' +
  1823. 't=0 0\r\n';
  1824. // first, take some things from the local description
  1825. for (var i = 0; i < this.localSDP.media.length; i++) {
  1826. cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
  1827. cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
  1828. if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
  1829. cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
  1830. }
  1831. cobbled += 'a=inactive\r\n';
  1832. }
  1833. this.remoteSDP = new SDP(cobbled);
  1834. }
  1835. // then add things like ice and dtls from remote candidate
  1836. elem.each(function () {
  1837. for (var i = 0; i < self.remoteSDP.media.length; i++) {
  1838. if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  1839. self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  1840. if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
  1841. var tmp = $(this).find('transport');
  1842. self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
  1843. self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
  1844. tmp = $(this).find('transport>fingerprint');
  1845. if (tmp.length) {
  1846. self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
  1847. } else {
  1848. console.log('no dtls fingerprint (webrtc issue #1718?)');
  1849. self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
  1850. }
  1851. break;
  1852. }
  1853. }
  1854. }
  1855. });
  1856. this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
  1857. // we need a complete SDP with ice-ufrag/ice-pwd in all parts
  1858. // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
  1859. // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
  1860. var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
  1861. return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
  1862. }).length == this.remoteSDP.media.length;
  1863. if (iscomplete) {
  1864. console.log('setting pranswer');
  1865. try {
  1866. this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
  1867. function() {
  1868. },
  1869. function(e) {
  1870. console.log('setRemoteDescription pranswer failed', e.toString());
  1871. });
  1872. } catch (e) {
  1873. console.error('setting pranswer failed', e);
  1874. }
  1875. } else {
  1876. //console.log('not yet setting pranswer');
  1877. }
  1878. }
  1879. // operate on each content element
  1880. elem.each(function () {
  1881. // would love to deactivate this, but firefox still requires it
  1882. var idx = -1;
  1883. var i;
  1884. for (i = 0; i < self.remoteSDP.media.length; i++) {
  1885. if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  1886. self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  1887. idx = i;
  1888. break;
  1889. }
  1890. }
  1891. if (idx == -1) { // fall back to localdescription
  1892. for (i = 0; i < self.localSDP.media.length; i++) {
  1893. if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  1894. self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  1895. idx = i;
  1896. break;
  1897. }
  1898. }
  1899. }
  1900. var name = $(this).attr('name');
  1901. // TODO: check ice-pwd and ice-ufrag?
  1902. $(this).find('transport>candidate').each(function () {
  1903. var line, candidate;
  1904. line = SDPUtil.candidateFromJingle(this);
  1905. candidate = new RTCIceCandidate({sdpMLineIndex: idx,
  1906. sdpMid: name,
  1907. candidate: line});
  1908. try {
  1909. self.peerconnection.addIceCandidate(candidate);
  1910. } catch (e) {
  1911. console.error('addIceCandidate failed', e.toString(), line);
  1912. }
  1913. });
  1914. });
  1915. };
  1916. JingleSession.prototype.sendAnswer = function (provisional) {
  1917. //console.log('createAnswer', provisional);
  1918. var self = this;
  1919. this.peerconnection.createAnswer(
  1920. function (sdp) {
  1921. self.createdAnswer(sdp, provisional);
  1922. },
  1923. function (e) {
  1924. console.error('createAnswer failed', e);
  1925. },
  1926. this.media_constraints
  1927. );
  1928. };
  1929. JingleSession.prototype.createdAnswer = function (sdp, provisional) {
  1930. //console.log('createAnswer callback');
  1931. var self = this;
  1932. this.localSDP = new SDP(sdp.sdp);
  1933. //this.localSDP.mangle();
  1934. this.usepranswer = provisional === true;
  1935. if (this.usetrickle) {
  1936. if (!this.usepranswer) {
  1937. var accept = $iq({to: this.peerjid,
  1938. type: 'set'})
  1939. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1940. action: 'session-accept',
  1941. initiator: this.initiator,
  1942. responder: this.responder,
  1943. sid: this.sid });
  1944. this.localSDP.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder');
  1945. this.connection.sendIQ(accept,
  1946. function () {
  1947. var ack = {};
  1948. ack.source = 'answer';
  1949. $(document).trigger('ack.jingle', [self.sid, ack]);
  1950. },
  1951. function (stanza) {
  1952. var error = ($(stanza).find('error').length) ? {
  1953. code: $(stanza).find('error').attr('code'),
  1954. reason: $(stanza).find('error :first')[0].tagName,
  1955. }:{};
  1956. error.source = 'answer';
  1957. $(document).trigger('error.jingle', [self.sid, error]);
  1958. },
  1959. 10000);
  1960. } else {
  1961. sdp.type = 'pranswer';
  1962. for (var i = 0; i < this.localSDP.media.length; i++) {
  1963. this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
  1964. }
  1965. this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
  1966. }
  1967. }
  1968. sdp.sdp = this.localSDP.raw;
  1969. this.peerconnection.setLocalDescription(sdp,
  1970. function () {
  1971. $(document).trigger('setLocalDescription.jingle', [self.sid]);
  1972. //console.log('setLocalDescription success');
  1973. },
  1974. function (e) {
  1975. console.error('setLocalDescription failed', e);
  1976. }
  1977. );
  1978. var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
  1979. for (var j = 0; j < cands.length; j++) {
  1980. var cand = SDPUtil.parse_icecandidate(cands[j]);
  1981. if (cand.type == 'srflx') {
  1982. this.hadstuncandidate = true;
  1983. } else if (cand.type == 'relay') {
  1984. this.hadturncandidate = true;
  1985. }
  1986. }
  1987. };
  1988. JingleSession.prototype.sendTerminate = function (reason, text) {
  1989. var self = this,
  1990. term = $iq({to: this.peerjid,
  1991. type: 'set'})
  1992. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1993. action: 'session-terminate',
  1994. initiator: this.initiator,
  1995. sid: this.sid})
  1996. .c('reason')
  1997. .c(reason || 'success');
  1998. if (text) {
  1999. term.up().c('text').t(text);
  2000. }
  2001. this.connection.sendIQ(term,
  2002. function () {
  2003. self.peerconnection.close();
  2004. self.peerconnection = null;
  2005. self.terminate();
  2006. var ack = {};
  2007. ack.source = 'terminate';
  2008. $(document).trigger('ack.jingle', [self.sid, ack]);
  2009. },
  2010. function (stanza) {
  2011. var error = ($(stanza).find('error').length) ? {
  2012. code: $(stanza).find('error').attr('code'),
  2013. reason: $(stanza).find('error :first')[0].tagName,
  2014. }:{};
  2015. $(document).trigger('ack.jingle', [self.sid, error]);
  2016. },
  2017. 10000);
  2018. if (this.statsinterval !== null) {
  2019. window.clearInterval(this.statsinterval);
  2020. this.statsinterval = null;
  2021. }
  2022. };
  2023. JingleSession.prototype.addSource = function (elem) {
  2024. console.log('addssrc', new Date().getTime());
  2025. console.log('ice', this.peerconnection.iceConnectionState);
  2026. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  2027. var self = this;
  2028. $(elem).each(function (idx, content) {
  2029. var name = $(content).attr('name');
  2030. var lines = '';
  2031. tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
  2032. tmp.each(function () {
  2033. var ssrc = $(this).attr('ssrc');
  2034. $(this).find('>parameter').each(function () {
  2035. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  2036. if ($(this).attr('value') && $(this).attr('value').length)
  2037. lines += ':' + $(this).attr('value');
  2038. lines += '\r\n';
  2039. });
  2040. });
  2041. sdp.media.forEach(function(media, idx) {
  2042. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  2043. return;
  2044. sdp.media[idx] += lines;
  2045. if (!self.addssrc[idx]) self.addssrc[idx] = '';
  2046. self.addssrc[idx] += lines;
  2047. });
  2048. sdp.raw = sdp.session + sdp.media.join('');
  2049. });
  2050. this.modifySources();
  2051. };
  2052. JingleSession.prototype.removeSource = function (elem) {
  2053. console.log('removessrc', new Date().getTime());
  2054. console.log('ice', this.peerconnection.iceConnectionState);
  2055. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  2056. var self = this;
  2057. $(elem).each(function (idx, content) {
  2058. var name = $(content).attr('name');
  2059. var lines = '';
  2060. tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
  2061. tmp.each(function () {
  2062. var ssrc = $(this).attr('ssrc');
  2063. $(this).find('>parameter').each(function () {
  2064. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  2065. if ($(this).attr('value') && $(this).attr('value').length)
  2066. lines += ':' + $(this).attr('value');
  2067. lines += '\r\n';
  2068. });
  2069. });
  2070. sdp.media.forEach(function(media, idx) {
  2071. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  2072. return;
  2073. sdp.media[idx] += lines;
  2074. if (!self.addssrc[idx]) self.removessrc[idx] = '';
  2075. self.removessrc[idx] += lines;
  2076. });
  2077. sdp.raw = sdp.session + sdp.media.join('');
  2078. });
  2079. this.modifySources();
  2080. };
  2081. JingleSession.prototype.modifySources = function() {
  2082. var self = this;
  2083. if (this.peerconnection.signalingState == 'closed') return;
  2084. if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null)) return;
  2085. if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
  2086. console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
  2087. this.wait = true;
  2088. window.setTimeout(function() { self.modifySources(); }, 250);
  2089. return;
  2090. }
  2091. if (this.wait) {
  2092. window.setTimeout(function() { self.modifySources(); }, 2500);
  2093. this.wait = false;
  2094. return;
  2095. }
  2096. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  2097. // add sources
  2098. this.addssrc.forEach(function(lines, idx) {
  2099. sdp.media[idx] += lines;
  2100. });
  2101. this.addssrc = [];
  2102. // remove sources
  2103. this.removessrc.forEach(function(lines, idx) {
  2104. lines = lines.split('\r\n');
  2105. lines.pop(); // remove empty last element;
  2106. lines.forEach(function(line) {
  2107. sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
  2108. });
  2109. });
  2110. this.removessrc = [];
  2111. sdp.raw = sdp.session + sdp.media.join('');
  2112. this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
  2113. function() {
  2114. self.peerconnection.createAnswer(
  2115. function(modifiedAnswer) {
  2116. // change video direction, see https://github.com/jitsi/jitmeet/issues/41
  2117. if (self.pendingop !== null) {
  2118. var sdp = new SDP(modifiedAnswer.sdp);
  2119. if (sdp.media.length > 1) {
  2120. switch(self.pendingop) {
  2121. case 'mute':
  2122. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  2123. break;
  2124. case 'unmute':
  2125. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  2126. break;
  2127. }
  2128. sdp.raw = sdp.session + sdp.media.join('');
  2129. modifiedAnswer.sdp = sdp.raw;
  2130. }
  2131. self.pendingop = null;
  2132. }
  2133. self.peerconnection.setLocalDescription(modifiedAnswer,
  2134. function() {
  2135. //console.log('modified setLocalDescription ok');
  2136. $(document).trigger('setLocalDescription.jingle', [self.sid]);
  2137. },
  2138. function(error) {
  2139. console.log('modified setLocalDescription failed');
  2140. }
  2141. );
  2142. },
  2143. function(error) {
  2144. console.log('modified answer failed');
  2145. }
  2146. );
  2147. },
  2148. function(error) {
  2149. console.log('modify failed');
  2150. }
  2151. );
  2152. };
  2153. // SDP-based mute by going recvonly/sendrecv
  2154. // FIXME: should probably black out the screen as well
  2155. JingleSession.prototype.hardMuteVideo = function (muted) {
  2156. this.pendingop = muted ? 'mute' : 'unmute';
  2157. this.modifySources();
  2158. this.connection.jingle.localStream.getVideoTracks().forEach(function (track) {
  2159. track.enabled = !muted;
  2160. });
  2161. };
  2162. JingleSession.prototype.sendMute = function (muted, content) {
  2163. var info = $iq({to: this.peerjid,
  2164. type: 'set'})
  2165. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  2166. action: 'session-info',
  2167. initiator: this.initiator,
  2168. sid: this.sid });
  2169. info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
  2170. info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
  2171. if (content) {
  2172. info.attrs({'name': content});
  2173. }
  2174. this.connection.send(info);
  2175. };
  2176. JingleSession.prototype.sendRinging = function () {
  2177. var info = $iq({to: this.peerjid,
  2178. type: 'set'})
  2179. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  2180. action: 'session-info',
  2181. initiator: this.initiator,
  2182. sid: this.sid });
  2183. info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
  2184. this.connection.send(info);
  2185. };
  2186. JingleSession.prototype.getStats = function (interval) {
  2187. var self = this;
  2188. var recv = {audio: 0, video: 0};
  2189. var lost = {audio: 0, video: 0};
  2190. var lastrecv = {audio: 0, video: 0};
  2191. var lastlost = {audio: 0, video: 0};
  2192. var loss = {audio: 0, video: 0};
  2193. var delta = {audio: 0, video: 0};
  2194. this.statsinterval = window.setInterval(function () {
  2195. if (self && self.peerconnection && self.peerconnection.getStats) {
  2196. self.peerconnection.getStats(function (stats) {
  2197. var results = stats.result();
  2198. // TODO: there are so much statistics you can get from this..
  2199. for (var i = 0; i < results.length; ++i) {
  2200. if (results[i].type == 'ssrc') {
  2201. var packetsrecv = results[i].stat('packetsReceived');
  2202. var packetslost = results[i].stat('packetsLost');
  2203. if (packetsrecv && packetslost) {
  2204. packetsrecv = parseInt(packetsrecv, 10);
  2205. packetslost = parseInt(packetslost, 10);
  2206. if (results[i].stat('googFrameRateReceived')) {
  2207. lastlost.video = lost.video;
  2208. lastrecv.video = recv.video;
  2209. recv.video = packetsrecv;
  2210. lost.video = packetslost;
  2211. } else {
  2212. lastlost.audio = lost.audio;
  2213. lastrecv.audio = recv.audio;
  2214. recv.audio = packetsrecv;
  2215. lost.audio = packetslost;
  2216. }
  2217. }
  2218. }
  2219. }
  2220. delta.audio = recv.audio - lastrecv.audio;
  2221. delta.video = recv.video - lastrecv.video;
  2222. loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
  2223. loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
  2224. $(document).trigger('packetloss.jingle', [self.sid, loss]);
  2225. });
  2226. }
  2227. }, interval || 3000);
  2228. return this.statsinterval;
  2229. };