| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 | 
							- /* jshint -W117 */
 - 
 - var JingleSession = require("./JingleSession");
 - var XMPPEvents = require("../../service/xmpp/XMPPEvents");
 - var RTCBrowserType = require("../RTC/RTCBrowserType");
 - 
 - 
 - module.exports = function(XMPP, eventEmitter) {
 -     function CallIncomingJingle(sid, connection) {
 -         var sess = connection.jingle.sessions[sid];
 - 
 -         // TODO: do we check activecall == null?
 -         connection.jingle.activecall = sess;
 - 
 -         eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
 - 
 -         // TODO: check affiliation and/or role
 -         console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
 -         sess.usedrip = true; // not-so-naive trickle ice
 -         sess.sendAnswer();
 -         sess.accept();
 - 
 -     }
 - 
 -     Strophe.addConnectionPlugin('jingle', {
 -         connection: null,
 -         sessions: {},
 -         jid2session: {},
 -         ice_config: {iceServers: []},
 -         pc_constraints: {},
 -         activecall: null,
 -         media_constraints: {
 -             mandatory: {
 -                 'OfferToReceiveAudio': true,
 -                 'OfferToReceiveVideo': true
 -             }
 -             // MozDontOfferDataChannel: true when this is firefox
 -         },
 -         init: function (conn) {
 -             this.connection = conn;
 -             if (this.connection.disco) {
 -                 // http://xmpp.org/extensions/xep-0167.html#support
 -                 // http://xmpp.org/extensions/xep-0176.html#support
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:1');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
 -                 this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
 - 
 -                 if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
 -                     || RTCBrowserType.isTemasysPluginUsed()) {
 -                     this.connection.disco.addFeature('urn:ietf:rfc:4588');
 -                 }
 - 
 -                 // this is dealt with by SDP O/A so we don't need to announce this
 -                 //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
 -                 //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
 - 
 -                 this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
 -                 this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
 - 
 -                 //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
 -             }
 -             this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
 -         },
 -         onJingle: function (iq) {
 -             var sid = $(iq).find('jingle').attr('sid');
 -             var action = $(iq).find('jingle').attr('action');
 -             var fromJid = iq.getAttribute('from');
 -             // send ack first
 -             var ack = $iq({type: 'result',
 -                 to: fromJid,
 -                 id: iq.getAttribute('id')
 -             });
 -             console.log('on jingle ' + action + ' from ' + fromJid, iq);
 -             var sess = this.sessions[sid];
 -             if ('session-initiate' != action) {
 -                 if (sess === null) {
 -                     ack.type = 'error';
 -                     ack.c('error', {type: 'cancel'})
 -                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
 -                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
 -                     this.connection.send(ack);
 -                     return true;
 -                 }
 -                 // local jid is not checked
 -                 if (fromJid != sess.peerjid) {
 -                     console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
 -                     ack.type = 'error';
 -                     ack.c('error', {type: 'cancel'})
 -                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
 -                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
 -                     this.connection.send(ack);
 -                     return true;
 -                 }
 -             } else if (sess !== undefined) {
 -                 // existing session with same session id
 -                 // this might be out-of-order if the sess.peerjid is the same as from
 -                 ack.type = 'error';
 -                 ack.c('error', {type: 'cancel'})
 -                     .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
 -                 console.warn('duplicate session id', sid);
 -                 this.connection.send(ack);
 -                 return true;
 -             }
 -             // FIXME: check for a defined action
 -             this.connection.send(ack);
 -             // see http://xmpp.org/extensions/xep-0166.html#concepts-session
 -             switch (action) {
 -                 case 'session-initiate':
 -                     var startMuted = $(iq).find('jingle>startmuted');
 -                     if (startMuted && startMuted.length > 0) {
 -                         var audioMuted = startMuted.attr("audio");
 -                         var videoMuted = startMuted.attr("video");
 -                         eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
 -                                 audioMuted === "true", videoMuted === "true");
 -                     }
 -                     sess = new JingleSession(
 -                         $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
 -                         this.connection, XMPP, eventEmitter);
 -                     // configure session
 - 
 -                     sess.media_constraints = this.media_constraints;
 -                     sess.pc_constraints = this.pc_constraints;
 -                     sess.ice_config = this.ice_config;
 - 
 -                     sess.initiate(fromJid, false);
 -                     // FIXME: setRemoteDescription should only be done when this call is to be accepted
 -                     sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
 - 
 -                     this.sessions[sess.sid] = sess;
 -                     this.jid2session[sess.peerjid] = sess;
 - 
 -                     // the callback should either
 -                     // .sendAnswer and .accept
 -                     // or .sendTerminate -- not necessarily synchronus
 -                     CallIncomingJingle(sess.sid, this.connection);
 -                     break;
 -                 case 'session-accept':
 -                     sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
 -                     sess.accept();
 -                     $(document).trigger('callaccepted.jingle', [sess.sid]);
 -                     break;
 -                 case 'session-terminate':
 -                     // If this is not the focus sending the terminate, we have
 -                     // nothing more to do here.
 -                     if (Object.keys(this.sessions).length < 1
 -                         || !(this.sessions[Object.keys(this.sessions)[0]]
 -                             instanceof JingleSession))
 -                     {
 -                         break;
 -                     }
 -                     console.log('terminating...', sess.sid);
 -                     sess.terminate();
 -                     this.terminate(sess.sid);
 -                     if ($(iq).find('>jingle>reason').length) {
 -                         $(document).trigger('callterminated.jingle', [
 -                             sess.sid,
 -                             sess.peerjid,
 -                             $(iq).find('>jingle>reason>:first')[0].tagName,
 -                             $(iq).find('>jingle>reason>text').text()
 -                         ]);
 -                     } else {
 -                         $(document).trigger('callterminated.jingle',
 -                             [sess.sid, sess.peerjid]);
 -                     }
 -                     break;
 -                 case 'transport-info':
 -                     sess.addIceCandidate($(iq).find('>jingle>content'));
 -                     break;
 -                 case 'session-info':
 -                     var affected;
 -                     if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
 -                         $(document).trigger('ringing.jingle', [sess.sid]);
 -                     } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
 -                         affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
 -                         $(document).trigger('mute.jingle', [sess.sid, affected]);
 -                     } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
 -                         affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
 -                         $(document).trigger('unmute.jingle', [sess.sid, affected]);
 -                     }
 -                     break;
 -                 case 'addsource': // FIXME: proprietary, un-jingleish
 -                 case 'source-add': // FIXME: proprietary
 -                     sess.addSource($(iq).find('>jingle>content'), fromJid);
 -                     break;
 -                 case 'removesource': // FIXME: proprietary, un-jingleish
 -                 case 'source-remove': // FIXME: proprietary
 -                     sess.removeSource($(iq).find('>jingle>content'), fromJid);
 -                     break;
 -                 default:
 -                     console.warn('jingle action not implemented', action);
 -                     break;
 -             }
 -             return true;
 -         },
 -         initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
 -             var sess = new JingleSession(myjid || this.connection.jid,
 -                 Math.random().toString(36).substr(2, 12), // random string
 -                 this.connection, XMPP, eventEmitter);
 -             // configure session
 - 
 -             sess.media_constraints = this.media_constraints;
 -             sess.pc_constraints = this.pc_constraints;
 -             sess.ice_config = this.ice_config;
 - 
 -             sess.initiate(peerjid, true);
 -             this.sessions[sess.sid] = sess;
 -             this.jid2session[sess.peerjid] = sess;
 -             sess.sendOffer();
 -             return sess;
 -         },
 -         terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
 -             if (sid === null || sid === undefined) {
 -                 for (sid in this.sessions) {
 -                     if (this.sessions[sid].state != 'ended') {
 -                         this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
 -                         this.sessions[sid].terminate();
 -                     }
 -                     delete this.jid2session[this.sessions[sid].peerjid];
 -                     delete this.sessions[sid];
 -                 }
 -             } else if (this.sessions.hasOwnProperty(sid)) {
 -                 if (this.sessions[sid].state != 'ended') {
 -                     this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
 -                     this.sessions[sid].terminate();
 -                 }
 -                 delete this.jid2session[this.sessions[sid].peerjid];
 -                 delete this.sessions[sid];
 -             }
 -         },
 -         // Used to terminate a session when an unavailable presence is received.
 -         terminateByJid: function (jid) {
 -             if (this.jid2session.hasOwnProperty(jid)) {
 -                 var sess = this.jid2session[jid];
 -                 if (sess) {
 -                     sess.terminate();
 -                     console.log('peer went away silently', jid);
 -                     delete this.sessions[sess.sid];
 -                     delete this.jid2session[jid];
 -                     $(document).trigger('callterminated.jingle',
 -                         [sess.sid, jid], 'gone');
 -                 }
 -             }
 -         },
 -         terminateRemoteByJid: function (jid, reason) {
 -             if (this.jid2session.hasOwnProperty(jid)) {
 -                 var sess = this.jid2session[jid];
 -                 if (sess) {
 -                     sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
 -                     sess.terminate();
 -                     console.log('terminate peer with jid', sess.sid, jid);
 -                     delete this.sessions[sess.sid];
 -                     delete this.jid2session[jid];
 -                     $(document).trigger('callterminated.jingle',
 -                         [sess.sid, jid, 'kicked']);
 -                 }
 -             }
 -         },
 -         getStunAndTurnCredentials: function () {
 -             // get stun and turn configuration from server via xep-0215
 -             // uses time-limited credentials as described in
 -             // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
 -             //
 -             // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
 -             // for a prosody module which implements this
 -             //
 -             // currently, this doesn't work with updateIce and therefore credentials with a long
 -             // validity have to be fetched before creating the peerconnection
 -             // TODO: implement refresh via updateIce as described in
 -             //      https://code.google.com/p/webrtc/issues/detail?id=1650
 -             var self = this;
 -             this.connection.sendIQ(
 -                 $iq({type: 'get', to: this.connection.domain})
 -                     .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
 -                 function (res) {
 -                     var iceservers = [];
 -                     $(res).find('>services>service').each(function (idx, el) {
 -                         el = $(el);
 -                         var dict = {};
 -                         var type = el.attr('type');
 -                         switch (type) {
 -                             case 'stun':
 -                                 dict.url = 'stun:' + el.attr('host');
 -                                 if (el.attr('port')) {
 -                                     dict.url += ':' + el.attr('port');
 -                                 }
 -                                 iceservers.push(dict);
 -                                 break;
 -                             case 'turn':
 -                             case 'turns':
 -                                 dict.url = type + ':';
 -                                 if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
 -                                     if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
 -                                         dict.url += el.attr('username') + '@';
 -                                     } else {
 -                                         dict.username = el.attr('username'); // only works in M28
 -                                     }
 -                                 }
 -                                 dict.url += el.attr('host');
 -                                 if (el.attr('port') && el.attr('port') != '3478') {
 -                                     dict.url += ':' + el.attr('port');
 -                                 }
 -                                 if (el.attr('transport') && el.attr('transport') != 'udp') {
 -                                     dict.url += '?transport=' + el.attr('transport');
 -                                 }
 -                                 if (el.attr('password')) {
 -                                     dict.credential = el.attr('password');
 -                                 }
 -                                 iceservers.push(dict);
 -                                 break;
 -                         }
 -                     });
 -                     self.ice_config.iceServers = iceservers;
 -                 },
 -                 function (err) {
 -                     console.warn('getting turn credentials failed', err);
 -                     console.warn('is mod_turncredentials or similar installed?');
 -                 }
 -             );
 -             // implement push?
 -         },
 - 
 -         /**
 -          * Returns the data saved in 'updateLog' in a format to be logged.
 -          */
 -         getLog: function () {
 -             var data = {};
 -             var self = this;
 -             Object.keys(this.sessions).forEach(function (sid) {
 -                 var session = self.sessions[sid];
 -                 if (session.peerconnection && session.peerconnection.updateLog) {
 -                     // FIXME: should probably be a .dump call
 -                     data["jingle_" + session.sid] = {
 -                         updateLog: session.peerconnection.updateLog,
 -                         stats: session.peerconnection.stats,
 -                         url: window.location.href
 -                     };
 -                 }
 -             });
 -             return data;
 -         }
 -     });
 - };
 
 
  |