modified lib-jitsi-meet dev repo
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

strophe.jingle.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /* global $, __filename */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. import { $iq, Strophe } from 'strophe.js';
  4. import {
  5. ACTION_JINGLE_TR_RECEIVED,
  6. ACTION_JINGLE_TR_SUCCESS,
  7. createJingleEvent
  8. } from '../../service/statistics/AnalyticsEvents';
  9. import XMPPEvents from '../../service/xmpp/XMPPEvents';
  10. import Statistics from '../statistics/statistics';
  11. import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
  12. import RandomUtil from '../util/RandomUtil';
  13. import ConnectionPlugin from './ConnectionPlugin';
  14. import JingleSessionPC from './JingleSessionPC';
  15. const logger = getLogger(__filename);
  16. // XXX Strophe is build around the idea of chaining function calls so allow long
  17. // function call chains.
  18. /* eslint-disable newline-per-chained-call */
  19. /**
  20. *
  21. */
  22. export default class JingleConnectionPlugin extends ConnectionPlugin {
  23. /**
  24. * Creates new <tt>JingleConnectionPlugin</tt>
  25. * @param {XMPP} xmpp
  26. * @param {EventEmitter} eventEmitter
  27. * @param {Object} iceConfig an object that holds the iceConfig to be passed
  28. * to the p2p and the jvb <tt>PeerConnection</tt>.
  29. */
  30. constructor(xmpp, eventEmitter, iceConfig) {
  31. super();
  32. this.xmpp = xmpp;
  33. this.eventEmitter = eventEmitter;
  34. this.sessions = {};
  35. this.jvbIceConfig = iceConfig.jvb;
  36. this.p2pIceConfig = iceConfig.p2p;
  37. this.mediaConstraints = {
  38. offerToReceiveAudio: true,
  39. offerToReceiveVideo: true
  40. };
  41. }
  42. /**
  43. *
  44. * @param connection
  45. */
  46. init(connection) {
  47. super.init(connection);
  48. this.connection.addHandler(this.onJingle.bind(this),
  49. 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
  50. }
  51. /**
  52. *
  53. * @param iq
  54. */
  55. onJingle(iq) {
  56. const sid = $(iq).find('jingle').attr('sid');
  57. const action = $(iq).find('jingle').attr('action');
  58. const fromJid = iq.getAttribute('from');
  59. // send ack first
  60. const ack = $iq({ type: 'result',
  61. to: fromJid,
  62. id: iq.getAttribute('id')
  63. });
  64. logger.log(`on jingle ${action} from ${fromJid}`, JSON.stringify(iq));
  65. let sess = this.sessions[sid];
  66. if (action !== 'session-initiate') {
  67. if (!sess) {
  68. ack.attrs({ type: 'error' });
  69. ack.c('error', { type: 'cancel' })
  70. .c('item-not-found', {
  71. xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
  72. })
  73. .up()
  74. .c('unknown-session', {
  75. xmlns: 'urn:xmpp:jingle:errors:1'
  76. });
  77. logger.warn('invalid session id', iq);
  78. this.connection.send(ack);
  79. return true;
  80. }
  81. // local jid is not checked
  82. if (fromJid !== sess.remoteJid) {
  83. logger.warn(
  84. 'jid mismatch for session id', sid, sess.remoteJid, iq);
  85. ack.attrs({ type: 'error' });
  86. ack.c('error', { type: 'cancel' })
  87. .c('item-not-found', {
  88. xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
  89. })
  90. .up()
  91. .c('unknown-session', {
  92. xmlns: 'urn:xmpp:jingle:errors:1'
  93. });
  94. this.connection.send(ack);
  95. return true;
  96. }
  97. } else if (sess !== undefined) {
  98. // Existing session with same session id. This might be out-of-order
  99. // if the sess.remoteJid is the same as from.
  100. ack.attrs({ type: 'error' });
  101. ack.c('error', { type: 'cancel' })
  102. .c('service-unavailable', {
  103. xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
  104. })
  105. .up();
  106. logger.warn('duplicate session id', sid, iq);
  107. this.connection.send(ack);
  108. return true;
  109. }
  110. const now = window.performance.now();
  111. // FIXME that should work most of the time, but we'd have to
  112. // think how secure it is to assume that user with "focus"
  113. // nickname is Jicofo.
  114. const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
  115. // see http://xmpp.org/extensions/xep-0166.html#concepts-session
  116. switch (action) {
  117. case 'session-initiate': {
  118. logger.log('(TIME) received session-initiate:\t', now);
  119. const startMuted = $(iq).find('jingle>startmuted');
  120. if (startMuted && startMuted.length > 0) {
  121. const audioMuted = startMuted.attr('audio');
  122. const videoMuted = startMuted.attr('video');
  123. this.eventEmitter.emit(
  124. XMPPEvents.START_MUTED_FROM_FOCUS,
  125. audioMuted === 'true',
  126. videoMuted === 'true');
  127. }
  128. logger.info(
  129. `Marking session from ${fromJid
  130. } as ${isP2P ? '' : '*not*'} P2P`);
  131. const iceConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;
  132. sess
  133. = new JingleSessionPC(
  134. $(iq).find('jingle').attr('sid'),
  135. $(iq).attr('to'),
  136. fromJid,
  137. this.connection,
  138. this.mediaConstraints,
  139. // Makes a copy in order to prevent exception thrown on RN when either this.p2pIceConfig or
  140. // this.jvbIceConfig is modified and there's a PeerConnection instance holding a reference
  141. JSON.parse(JSON.stringify(iceConfig)),
  142. isP2P,
  143. /* initiator */ false);
  144. this.sessions[sess.sid] = sess;
  145. this.eventEmitter.emit(XMPPEvents.CALL_INCOMING,
  146. sess, $(iq).find('>jingle'), now);
  147. break;
  148. }
  149. case 'session-accept': {
  150. this.eventEmitter.emit(
  151. XMPPEvents.CALL_ACCEPTED, sess, $(iq).find('>jingle'));
  152. break;
  153. }
  154. case 'content-modify': {
  155. sess.modifyContents($(iq).find('>jingle'));
  156. break;
  157. }
  158. case 'transport-info': {
  159. this.eventEmitter.emit(
  160. XMPPEvents.TRANSPORT_INFO, sess, $(iq).find('>jingle'));
  161. break;
  162. }
  163. case 'session-terminate': {
  164. logger.log('terminating...', sess.sid);
  165. let reasonCondition = null;
  166. let reasonText = null;
  167. if ($(iq).find('>jingle>reason').length) {
  168. reasonCondition
  169. = $(iq).find('>jingle>reason>:first')[0].tagName;
  170. reasonText = $(iq).find('>jingle>reason>text').text();
  171. }
  172. this.terminate(sess.sid, reasonCondition, reasonText);
  173. this.eventEmitter.emit(XMPPEvents.CALL_ENDED,
  174. sess, reasonCondition, reasonText);
  175. break;
  176. }
  177. case 'transport-replace':
  178. logger.info('(TIME) Start transport replace:\t', now);
  179. Statistics.sendAnalytics(createJingleEvent(
  180. ACTION_JINGLE_TR_RECEIVED,
  181. {
  182. p2p: isP2P,
  183. value: now
  184. }));
  185. sess.replaceTransport($(iq).find('>jingle'), () => {
  186. const successTime = window.performance.now();
  187. logger.info('(TIME) Transport replace success:\t', successTime);
  188. Statistics.sendAnalytics(createJingleEvent(
  189. ACTION_JINGLE_TR_SUCCESS,
  190. {
  191. p2p: isP2P,
  192. value: successTime
  193. }));
  194. }, error => {
  195. GlobalOnErrorHandler.callErrorHandler(error);
  196. logger.error('Transport replace failed', error);
  197. sess.sendTransportReject();
  198. });
  199. break;
  200. case 'addsource': // FIXME: proprietary, un-jingleish
  201. case 'source-add': // FIXME: proprietary
  202. sess.addRemoteStream($(iq).find('>jingle>content'));
  203. break;
  204. case 'removesource': // FIXME: proprietary, un-jingleish
  205. case 'source-remove': // FIXME: proprietary
  206. sess.removeRemoteStream($(iq).find('>jingle>content'));
  207. break;
  208. default:
  209. logger.warn('jingle action not implemented', action);
  210. ack.attrs({ type: 'error' });
  211. ack.c('error', { type: 'cancel' })
  212. .c('bad-request',
  213. { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
  214. .up();
  215. break;
  216. }
  217. this.connection.send(ack);
  218. return true;
  219. }
  220. /**
  221. * Creates new <tt>JingleSessionPC</tt> meant to be used in a direct P2P
  222. * connection, configured as 'initiator'.
  223. * @param {string} me our JID
  224. * @param {string} peer remote participant's JID
  225. * @return {JingleSessionPC}
  226. */
  227. newP2PJingleSession(me, peer) {
  228. const sess
  229. = new JingleSessionPC(
  230. RandomUtil.randomHexString(12),
  231. me,
  232. peer,
  233. this.connection,
  234. this.mediaConstraints,
  235. this.p2pIceConfig,
  236. /* P2P */ true,
  237. /* initiator */ true);
  238. this.sessions[sess.sid] = sess;
  239. return sess;
  240. }
  241. /**
  242. *
  243. * @param sid
  244. * @param reasonCondition
  245. * @param reasonText
  246. */
  247. terminate(sid, reasonCondition, reasonText) {
  248. if (this.sessions.hasOwnProperty(sid)) {
  249. if (this.sessions[sid].state !== 'ended') {
  250. this.sessions[sid].onTerminated(reasonCondition, reasonText);
  251. }
  252. delete this.sessions[sid];
  253. }
  254. }
  255. /**
  256. *
  257. */
  258. getStunAndTurnCredentials() {
  259. // get stun and turn configuration from server via xep-0215
  260. // uses time-limited credentials as described in
  261. // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
  262. //
  263. // See https://modules.prosody.im/mod_turncredentials.html
  264. // for a prosody module which implements this.
  265. // Or the new implementation https://modules.prosody.im/mod_external_services which will be in prosody 0.12
  266. //
  267. // Currently, this doesn't work with updateIce and therefore credentials
  268. // with a long validity have to be fetched before creating the
  269. // peerconnection.
  270. // TODO: implement refresh via updateIce as described in
  271. // https://code.google.com/p/webrtc/issues/detail?id=1650
  272. this.connection.sendIQ(
  273. $iq({ type: 'get',
  274. to: this.xmpp.options.hosts.domain })
  275. .c('services', { xmlns: 'urn:xmpp:extdisco:2' }),
  276. v2Res => this.onReceiveStunAndTurnCredentials(v2Res),
  277. v2Err => {
  278. logger.warn('getting turn credentials with extdisco:2 failed, trying extdisco:1', v2Err);
  279. this.connection.sendIQ(
  280. $iq({ type: 'get',
  281. to: this.xmpp.options.hosts.domain })
  282. .c('services', { xmlns: 'urn:xmpp:extdisco:1' }),
  283. v1Res => this.onReceiveStunAndTurnCredentials(v1Res),
  284. v1Err => {
  285. logger.warn('getting turn credentials failed', v1Err);
  286. logger.warn('is mod_turncredentials or similar installed and configured?');
  287. }
  288. );
  289. });
  290. }
  291. /**
  292. * Parses response when querying for services using urn:xmpp:extdisco:1 or urn:xmpp:extdisco:2.
  293. * Stores results in jvbIceConfig and p2pIceConfig.
  294. * @param res The response iq.
  295. * @return {boolean} Whether something was processed from the supplied message.
  296. */
  297. onReceiveStunAndTurnCredentials(res) {
  298. const iceservers = [];
  299. $(res).find('>services>service').each((idx, el) => {
  300. // eslint-disable-next-line no-param-reassign
  301. el = $(el);
  302. const dict = {};
  303. const type = el.attr('type');
  304. switch (type) {
  305. case 'stun':
  306. dict.urls = `stun:${el.attr('host')}`;
  307. if (el.attr('port')) {
  308. dict.urls += `:${el.attr('port')}`;
  309. }
  310. iceservers.push(dict);
  311. break;
  312. case 'turn':
  313. case 'turns': {
  314. dict.urls = `${type}:`;
  315. dict.username = el.attr('username');
  316. dict.urls += el.attr('host');
  317. const port = el.attr('port');
  318. if (port) {
  319. dict.urls += `:${el.attr('port')}`;
  320. }
  321. const transport = el.attr('transport');
  322. if (transport && transport !== 'udp') {
  323. dict.urls += `?transport=${transport}`;
  324. }
  325. dict.credential = el.attr('password')
  326. || dict.credential;
  327. iceservers.push(dict);
  328. break;
  329. }
  330. }
  331. });
  332. const options = this.xmpp.options;
  333. // Shuffle ICEServers for loadbalancing
  334. for (let i = iceservers.length - 1; i > 0; i--) {
  335. const j = Math.floor(Math.random() * (i + 1));
  336. const temp = iceservers[i];
  337. iceservers[i] = iceservers[j];
  338. iceservers[j] = temp;
  339. }
  340. let filter;
  341. if (options.useTurnUdp) {
  342. filter = s => s.urls.startsWith('turn');
  343. } else {
  344. // By default we filter out STUN and TURN/UDP and leave only TURN/TCP.
  345. filter = s => s.urls.startsWith('turn') && (s.urls.indexOf('transport=tcp') >= 0);
  346. }
  347. this.jvbIceConfig.iceServers = iceservers.filter(filter);
  348. this.p2pIceConfig.iceServers = iceservers;
  349. return iceservers.length > 0;
  350. }
  351. /**
  352. * Returns the data saved in 'updateLog' in a format to be logged.
  353. */
  354. getLog() {
  355. const data = {};
  356. Object.keys(this.sessions).forEach(sid => {
  357. const session = this.sessions[sid];
  358. const pc = session.peerconnection;
  359. if (pc && pc.updateLog) {
  360. // FIXME: should probably be a .dump call
  361. data[`jingle_${sid}`] = {
  362. updateLog: pc.updateLog,
  363. stats: pc.stats,
  364. url: window.location.href
  365. };
  366. }
  367. });
  368. return data;
  369. }
  370. }
  371. /* eslint-enable newline-per-chained-call */