您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

strophejingle.bundle.js 153KB

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