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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /* jshint -W117 */
  2. var logger = require("jitsi-meet-logger").getLogger(__filename);
  3. var JingleSession = require("./JingleSessionPC");
  4. var XMPPEvents = require("../../service/xmpp/XMPPEvents");
  5. var RTCBrowserType = require("../RTC/RTCBrowserType");
  6. var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
  7. var Statistics = require("../statistics/statistics");
  8. module.exports = function(XMPP, eventEmitter) {
  9. Strophe.addConnectionPlugin('jingle', {
  10. connection: null,
  11. sessions: {},
  12. ice_config: {iceServers: []},
  13. media_constraints: {
  14. mandatory: {
  15. 'OfferToReceiveAudio': true,
  16. 'OfferToReceiveVideo': true
  17. }
  18. // MozDontOfferDataChannel: true when this is firefox
  19. },
  20. init: function (conn) {
  21. this.connection = conn;
  22. this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
  23. },
  24. onJingle: function (iq) {
  25. var sid = $(iq).find('jingle').attr('sid');
  26. var action = $(iq).find('jingle').attr('action');
  27. var fromJid = iq.getAttribute('from');
  28. // send ack first
  29. var ack = $iq({type: 'result',
  30. to: fromJid,
  31. id: iq.getAttribute('id')
  32. });
  33. logger.log('on jingle ' + action + ' from ' + fromJid, iq);
  34. var sess = this.sessions[sid];
  35. if ('session-initiate' != action) {
  36. if (!sess) {
  37. ack.attrs({ type: 'error' });
  38. ack.c('error', {type: 'cancel'})
  39. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  40. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  41. logger.warn('invalid session id', iq);
  42. this.connection.send(ack);
  43. return true;
  44. }
  45. // local jid is not checked
  46. if (fromJid != sess.peerjid) {
  47. logger.warn(
  48. 'jid mismatch for session id', sid, sess.peerjid, iq);
  49. ack.attrs({ type: 'error' });
  50. ack.c('error', {type: 'cancel'})
  51. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  52. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  53. this.connection.send(ack);
  54. return true;
  55. }
  56. } else if (sess !== undefined) {
  57. // existing session with same session id
  58. // this might be out-of-order if the sess.peerjid is the same as from
  59. ack.attrs({ type: 'error' });
  60. ack.c('error', {type: 'cancel'})
  61. .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
  62. logger.warn('duplicate session id', sid, iq);
  63. this.connection.send(ack);
  64. return true;
  65. }
  66. // see http://xmpp.org/extensions/xep-0166.html#concepts-session
  67. switch (action) {
  68. case 'session-initiate':
  69. var now = window.performance.now();
  70. logger.log("(TIME) received session-initiate:\t", now);
  71. var startMuted = $(iq).find('jingle>startmuted');
  72. if (startMuted && startMuted.length > 0) {
  73. var audioMuted = startMuted.attr("audio");
  74. var videoMuted = startMuted.attr("video");
  75. eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
  76. audioMuted === "true", videoMuted === "true");
  77. }
  78. sess = new JingleSession(
  79. $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
  80. fromJid,
  81. this.connection,
  82. this.media_constraints,
  83. this.ice_config, XMPP);
  84. this.sessions[sess.sid] = sess;
  85. var jingleOffer = $(iq).find('>jingle');
  86. // FIXME there's no nice way with event to get the reason
  87. // why the call was rejected
  88. eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleOffer, now);
  89. if (!sess.active())
  90. {
  91. // Call not accepted
  92. ack.attrs({ type: 'error' });
  93. ack.c('error', {type: 'cancel'})
  94. .c('bad-request',
  95. { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
  96. .up();
  97. this.terminate(sess.sid);
  98. }
  99. Statistics.analytics.sendEvent(
  100. 'xmpp.session-initiate', now);
  101. break;
  102. case 'session-terminate':
  103. logger.log('terminating...', sess.sid);
  104. var reasonCondition = null;
  105. var reasonText = null;
  106. if ($(iq).find('>jingle>reason').length) {
  107. reasonCondition
  108. = $(iq).find('>jingle>reason>:first')[0].tagName;
  109. reasonText = $(iq).find('>jingle>reason>text').text();
  110. }
  111. this.terminate(sess.sid, reasonCondition, reasonText);
  112. eventEmitter.emit(XMPPEvents.CALL_ENDED,
  113. sess, reasonCondition, reasonText);
  114. break;
  115. case 'transport-replace':
  116. var now = window.performance.now();
  117. logger.info("(TIME) Start transport replace", now);
  118. Statistics.analytics.sendEvent(
  119. 'xmpp.transport-replace.start', now);
  120. sess.replaceTransport($(iq).find('>jingle'),
  121. function () {
  122. var now = window.performance.now();
  123. logger.info(
  124. "(TIME) Transport replace success!", now);
  125. Statistics.analytics.sendEvent(
  126. 'xmpp.transport-replace.success', now);
  127. },
  128. function(error) {
  129. GlobalOnErrorHandler.callErrorHandler(error);
  130. logger.error('Transport replace failed', error);
  131. sess.sendTransportReject();
  132. });
  133. break;
  134. case 'addsource': // FIXME: proprietary, un-jingleish
  135. case 'source-add': // FIXME: proprietary
  136. sess.addSource($(iq).find('>jingle>content'));
  137. break;
  138. case 'removesource': // FIXME: proprietary, un-jingleish
  139. case 'source-remove': // FIXME: proprietary
  140. sess.removeSource($(iq).find('>jingle>content'));
  141. break;
  142. default:
  143. logger.warn('jingle action not implemented', action);
  144. ack.attrs({ type: 'error' });
  145. ack.c('error', {type: 'cancel'})
  146. .c('bad-request',
  147. { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
  148. .up();
  149. break;
  150. }
  151. this.connection.send(ack);
  152. return true;
  153. },
  154. terminate: function (sid, reasonCondition, reasonText) {
  155. if (this.sessions.hasOwnProperty(sid)) {
  156. if (this.sessions[sid].state != 'ended') {
  157. this.sessions[sid].onTerminated(reasonCondition, reasonText);
  158. }
  159. delete this.sessions[sid];
  160. }
  161. },
  162. getStunAndTurnCredentials: function () {
  163. // get stun and turn configuration from server via xep-0215
  164. // uses time-limited credentials as described in
  165. // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
  166. //
  167. // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
  168. // for a prosody module which implements this
  169. //
  170. // currently, this doesn't work with updateIce and therefore credentials with a long
  171. // validity have to be fetched before creating the peerconnection
  172. // TODO: implement refresh via updateIce as described in
  173. // https://code.google.com/p/webrtc/issues/detail?id=1650
  174. var self = this;
  175. this.connection.sendIQ(
  176. $iq({type: 'get', to: this.connection.domain})
  177. .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
  178. function (res) {
  179. var iceservers = [];
  180. $(res).find('>services>service').each(function (idx, el) {
  181. el = $(el);
  182. var dict = {};
  183. var type = el.attr('type');
  184. switch (type) {
  185. case 'stun':
  186. dict.url = 'stun:' + el.attr('host');
  187. if (el.attr('port')) {
  188. dict.url += ':' + el.attr('port');
  189. }
  190. iceservers.push(dict);
  191. break;
  192. case 'turn':
  193. case 'turns':
  194. dict.url = type + ':';
  195. if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
  196. if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
  197. dict.url += el.attr('username') + '@';
  198. } else {
  199. dict.username = el.attr('username'); // only works in M28
  200. }
  201. }
  202. dict.url += el.attr('host');
  203. if (el.attr('port') && el.attr('port') != '3478') {
  204. dict.url += ':' + el.attr('port');
  205. }
  206. if (el.attr('transport') && el.attr('transport') != 'udp') {
  207. dict.url += '?transport=' + el.attr('transport');
  208. }
  209. if (el.attr('password')) {
  210. dict.credential = el.attr('password');
  211. }
  212. iceservers.push(dict);
  213. break;
  214. }
  215. });
  216. self.ice_config.iceServers = iceservers;
  217. },
  218. function (err) {
  219. logger.warn('getting turn credentials failed', err);
  220. logger.warn('is mod_turncredentials or similar installed?');
  221. }
  222. );
  223. // implement push?
  224. },
  225. /**
  226. * Returns the data saved in 'updateLog' in a format to be logged.
  227. */
  228. getLog: function () {
  229. var data = {};
  230. var self = this;
  231. Object.keys(this.sessions).forEach(function (sid) {
  232. var session = self.sessions[sid];
  233. if (session.peerconnection && session.peerconnection.updateLog) {
  234. // FIXME: should probably be a .dump call
  235. data["jingle_" + session.sid] = {
  236. updateLog: session.peerconnection.updateLog,
  237. stats: session.peerconnection.stats,
  238. url: window.location.href
  239. };
  240. }
  241. });
  242. return data;
  243. }
  244. });
  245. };