123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /* global $, $iq, Strophe */
-
- import { getLogger } from "jitsi-meet-logger";
- const logger = getLogger(__filename);
- import JingleSession from "./JingleSessionPC";
- import XMPPEvents from "../../service/xmpp/XMPPEvents";
- import GlobalOnErrorHandler from "../util/GlobalOnErrorHandler";
- import Statistics from "../statistics/statistics";
- import ConnectionPlugin from "./ConnectionPlugin";
-
- class JingleConnectionPlugin extends ConnectionPlugin {
- constructor(xmpp, eventEmitter) {
- super();
- this.xmpp = xmpp;
- this.eventEmitter = eventEmitter;
- this.sessions = {};
- this.ice_config = {iceServers: []};
- this.media_constraints = {
- mandatory: {
- 'OfferToReceiveAudio': true,
- 'OfferToReceiveVideo': true
- }
- // MozDontOfferDataChannel: true when this is firefox
- };
- }
-
- init (connection) {
- super.init(connection);
- this.connection.addHandler(this.onJingle.bind(this),
- 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
- }
-
- onJingle (iq) {
- const sid = $(iq).find('jingle').attr('sid');
- const action = $(iq).find('jingle').attr('action');
- const fromJid = iq.getAttribute('from');
- // send ack first
- const ack = $iq({type: 'result',
- to: fromJid,
- id: iq.getAttribute('id')
- });
- logger.log('on jingle ' + action + ' from ' + fromJid, iq);
- let sess = this.sessions[sid];
- if ('session-initiate' != action) {
- if (!sess) {
- ack.attrs({ 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'});
- logger.warn('invalid session id', iq);
- this.connection.send(ack);
- return true;
- }
- // local jid is not checked
- if (fromJid != sess.peerjid) {
- logger.warn(
- 'jid mismatch for session id', sid, sess.peerjid, iq);
- ack.attrs({ 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.attrs({ type: 'error' });
- ack.c('error', {type: 'cancel'})
- .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
- logger.warn('duplicate session id', sid, iq);
- this.connection.send(ack);
- return true;
- }
- const now = window.performance.now();
- // see http://xmpp.org/extensions/xep-0166.html#concepts-session
- switch (action) {
- case 'session-initiate': {
- logger.log("(TIME) received session-initiate:\t", now);
- const startMuted = $(iq).find('jingle>startmuted');
- if (startMuted && startMuted.length > 0) {
- const audioMuted = startMuted.attr("audio");
- const videoMuted = startMuted.attr("video");
- this.eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
- audioMuted === "true", videoMuted === "true");
- }
- sess = new JingleSession(
- $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
- fromJid,
- this.connection,
- this.media_constraints,
- this.ice_config, this.xmpp);
-
- this.sessions[sess.sid] = sess;
-
- this.eventEmitter.emit(XMPPEvents.CALL_INCOMING,
- sess, $(iq).find('>jingle'), now);
- Statistics.analytics.sendEvent(
- 'xmpp.session-initiate', {value: now});
- break;
- }
- case 'session-terminate': {
- logger.log('terminating...', sess.sid);
- let reasonCondition = null;
- let reasonText = null;
- if ($(iq).find('>jingle>reason').length) {
- reasonCondition
- = $(iq).find('>jingle>reason>:first')[0].tagName;
- reasonText = $(iq).find('>jingle>reason>text').text();
- }
- this.terminate(sess.sid, reasonCondition, reasonText);
- this.eventEmitter.emit(XMPPEvents.CALL_ENDED,
- sess, reasonCondition, reasonText);
- break;
- }
- case 'transport-replace':
- logger.info("(TIME) Start transport replace", now);
- Statistics.analytics.sendEvent(
- 'xmpp.transport-replace.start', {value: now});
-
- sess.replaceTransport($(iq).find('>jingle'), () => {
- const successTime = window.performance.now();
- logger.info(
- "(TIME) Transport replace success!", successTime);
- Statistics.analytics.sendEvent(
- 'xmpp.transport-replace.success',
- {value: successTime});
- }, error => {
- GlobalOnErrorHandler.callErrorHandler(error);
- logger.error('Transport replace failed', error);
- sess.sendTransportReject();
- });
- break;
- case 'addsource': // FIXME: proprietary, un-jingleish
- case 'source-add': // FIXME: proprietary
- sess.addSource($(iq).find('>jingle>content'));
- break;
- case 'removesource': // FIXME: proprietary, un-jingleish
- case 'source-remove': // FIXME: proprietary
- sess.removeSource($(iq).find('>jingle>content'));
- break;
- default:
- logger.warn('jingle action not implemented', action);
- ack.attrs({ type: 'error' });
- ack.c('error', {type: 'cancel'})
- .c('bad-request',
- { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
- .up();
- break;
- }
- this.connection.send(ack);
- return true;
- }
-
- terminate (sid, reasonCondition, reasonText) {
- if (this.sessions.hasOwnProperty(sid)) {
- if (this.sessions[sid].state != 'ended') {
- this.sessions[sid].onTerminated(reasonCondition, reasonText);
- }
- delete this.sessions[sid];
- }
- }
-
- getStunAndTurnCredentials () {
- // 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
- this.connection.sendIQ(
- $iq({type: 'get', to: this.connection.domain})
- .c('services', {xmlns: 'urn:xmpp:extdisco:1'})
- .c('service', {host: 'turn.' + this.connection.domain}),
- res => {
- let iceservers = [];
- $(res).find('>services>service').each((idx, el) => {
- el = $(el);
- let dict = {};
- const 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 + ':';
- const username = el.attr('username');
- // https://code.google.com/p/webrtc/issues/detail?id=1508
- if (username) {
- if (navigator.userAgent.match(
- /Chrom(e|ium)\/([0-9]+)\./)
- && parseInt(
- navigator.userAgent.match(
- /Chrom(e|ium)\/([0-9]+)\./)[2],
- 10) < 28) {
- dict.url += username + '@';
- } else {
- // only works in M28
- dict.username = username;
- }
- }
- dict.url += el.attr('host');
- const port = el.attr('port');
- if (port && port != '3478') {
- dict.url += ':' + el.attr('port');
- }
- const transport = el.attr('transport');
- if (transport && transport != 'udp') {
- dict.url += '?transport=' + transport;
- }
-
- dict.credential = el.attr('password')
- || dict.credential;
- iceservers.push(dict);
- break;
- }
- }
- });
- this.ice_config.iceServers = iceservers;
- }, err => {
- logger.warn('getting turn credentials failed', err);
- logger.warn('is mod_turncredentials or similar installed?');
- });
- // implement push?
- }
-
- /**
- * Returns the data saved in 'updateLog' in a format to be logged.
- */
- getLog () {
- const data = {};
- Object.keys(this.sessions).forEach(sid => {
- const session = this.sessions[sid];
- const pc = session.peerconnection;
- if (pc && pc.updateLog) {
- // FIXME: should probably be a .dump call
- data["jingle_" + sid] = {
- updateLog: pc.updateLog,
- stats: pc.stats,
- url: window.location.href
- };
- }
- });
- return data;
- }
- }
-
-
-
- module.exports = function(XMPP, eventEmitter) {
- Strophe.addConnectionPlugin('jingle',
- new JingleConnectionPlugin(XMPP, eventEmitter));
- };
|