modified lib-jitsi-meet dev repo
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

xmpp.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /* global $, Strophe */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. const logger = getLogger(__filename);
  4. import RandomUtil from '../util/RandomUtil';
  5. import * as JitsiConnectionErrors from '../../JitsiConnectionErrors';
  6. import * as JitsiConnectionEvents from '../../JitsiConnectionEvents';
  7. import RTCBrowserType from '../RTC/RTCBrowserType';
  8. import initEmuc from './strophe.emuc';
  9. import initJingle from './strophe.jingle';
  10. import initStropheUtil from './strophe.util';
  11. import initPing from './strophe.ping';
  12. import initRayo from './strophe.rayo';
  13. import initStropheLogger from './strophe.logger';
  14. import Listenable from '../util/Listenable';
  15. import Caps from './Caps';
  16. /**
  17. *
  18. * @param token
  19. * @param bosh
  20. */
  21. function createConnection(token, bosh = '/http-bind') {
  22. // Append token as URL param
  23. if (token) {
  24. // eslint-disable-next-line no-param-reassign
  25. bosh += `${bosh.indexOf('?') === -1 ? '?' : '&'}token=${token}`;
  26. }
  27. return new Strophe.Connection(bosh);
  28. }
  29. /**
  30. *
  31. */
  32. export default class XMPP extends Listenable {
  33. /**
  34. *
  35. * @param options
  36. * @param token
  37. */
  38. constructor(options, token) {
  39. super();
  40. this.connection = null;
  41. this.disconnectInProgress = false;
  42. this.connectionTimes = {};
  43. this.forceMuted = false;
  44. this.options = options;
  45. this.connectParams = {};
  46. this.token = token;
  47. this.authenticatedUser = false;
  48. this._initStrophePlugins(this);
  49. this.connection = createConnection(token, options.bosh);
  50. this.caps = new Caps(this.connection, this.options.clientNode);
  51. // Initialize features advertised in disco-info
  52. this.initFeaturesList();
  53. // Setup a disconnect on unload as a way to facilitate API consumers. It
  54. // sounds like they would want that. A problem for them though may be if
  55. // they wanted to utilize the connected connection in an unload handler
  56. // of their own. However, it should be fairly easy for them to do that
  57. // by registering their unload handler before us.
  58. $(window).on('beforeunload unload', this.disconnect.bind(this));
  59. }
  60. /**
  61. * Initializes the list of feature advertised through the disco-info
  62. * mechanism.
  63. */
  64. initFeaturesList() {
  65. // http://xmpp.org/extensions/xep-0167.html#support
  66. // http://xmpp.org/extensions/xep-0176.html#support
  67. this.caps.addFeature('urn:xmpp:jingle:1');
  68. this.caps.addFeature('urn:xmpp:jingle:apps:rtp:1');
  69. this.caps.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
  70. this.caps.addFeature('urn:xmpp:jingle:apps:dtls:0');
  71. this.caps.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
  72. this.caps.addFeature('urn:xmpp:jingle:apps:rtp:audio');
  73. this.caps.addFeature('urn:xmpp:jingle:apps:rtp:video');
  74. if (!this.options.disableRtx && !RTCBrowserType.isFirefox()) {
  75. this.caps.addFeature('urn:ietf:rfc:4588');
  76. }
  77. // this is dealt with by SDP O/A so we don't need to announce this
  78. // XEP-0293
  79. // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0');
  80. // XEP-0294
  81. // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0');
  82. this.caps.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
  83. this.caps.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
  84. // this.caps.addFeature('urn:ietf:rfc:5576'); // a=ssrc
  85. // Enable Lipsync ?
  86. if (RTCBrowserType.isChrome() && this.options.enableLipSync !== false) {
  87. logger.info('Lip-sync enabled !');
  88. this.caps.addFeature('http://jitsi.org/meet/lipsync');
  89. }
  90. if (this.connection.rayo) {
  91. this.caps.addFeature('urn:xmpp:rayo:client:1');
  92. }
  93. }
  94. /**
  95. *
  96. */
  97. getConnection() {
  98. return this.connection;
  99. }
  100. /**
  101. * Receive connection status changes and handles them.
  102. * @password {string} the password passed in connect method
  103. * @status the connection status
  104. * @msg message
  105. */
  106. connectionHandler(password, status, msg) {
  107. const now = window.performance.now();
  108. const statusStr = Strophe.getStatusString(status).toLowerCase();
  109. this.connectionTimes[statusStr] = now;
  110. logger.log(
  111. `(TIME) Strophe ${statusStr}${msg ? `[${msg}]` : ''}:\t`,
  112. now);
  113. if (status === Strophe.Status.CONNECTED
  114. || status === Strophe.Status.ATTACHED) {
  115. if (this.options.useStunTurn) {
  116. this.connection.jingle.getStunAndTurnCredentials();
  117. }
  118. logger.info(`My Jabber ID: ${this.connection.jid}`);
  119. // Schedule ping ?
  120. const pingJid = this.connection.domain;
  121. this.connection.ping.hasPingSupport(
  122. pingJid,
  123. hasPing => {
  124. if (hasPing) {
  125. this.connection.ping.startInterval(pingJid);
  126. } else {
  127. logger.warn(`Ping NOT supported by ${pingJid}`);
  128. }
  129. });
  130. if (password) {
  131. this.authenticatedUser = true;
  132. }
  133. if (this.connection && this.connection.connected
  134. && Strophe.getResourceFromJid(this.connection.jid)) {
  135. // .connected is true while connecting?
  136. // this.connection.send($pres());
  137. this.eventEmitter.emit(
  138. JitsiConnectionEvents.CONNECTION_ESTABLISHED,
  139. Strophe.getResourceFromJid(this.connection.jid));
  140. }
  141. } else if (status === Strophe.Status.CONNFAIL) {
  142. if (msg === 'x-strophe-bad-non-anon-jid') {
  143. this.anonymousConnectionFailed = true;
  144. } else {
  145. this.connectionFailed = true;
  146. }
  147. this.lastErrorMsg = msg;
  148. } else if (status === Strophe.Status.DISCONNECTED) {
  149. // Stop ping interval
  150. this.connection.ping.stopInterval();
  151. const wasIntentionalDisconnect = this.disconnectInProgress;
  152. const errMsg = msg ? msg : this.lastErrorMsg;
  153. this.disconnectInProgress = false;
  154. if (this.anonymousConnectionFailed) {
  155. // prompt user for username and password
  156. this.eventEmitter.emit(
  157. JitsiConnectionEvents.CONNECTION_FAILED,
  158. JitsiConnectionErrors.PASSWORD_REQUIRED);
  159. } else if (this.connectionFailed) {
  160. this.eventEmitter.emit(
  161. JitsiConnectionEvents.CONNECTION_FAILED,
  162. JitsiConnectionErrors.OTHER_ERROR, errMsg);
  163. } else if (wasIntentionalDisconnect) {
  164. this.eventEmitter.emit(
  165. JitsiConnectionEvents.CONNECTION_DISCONNECTED, errMsg);
  166. } else {
  167. // XXX if Strophe drops the connection while not being asked to,
  168. // it means that most likely some serious error has occurred.
  169. // One currently known case is when a BOSH request fails for
  170. // more than 4 times. The connection is dropped without
  171. // supplying a reason(error message/event) through the API.
  172. logger.error('XMPP connection dropped!');
  173. // XXX if the last request error is within 5xx range it means it
  174. // was a server failure
  175. const lastErrorStatus = Strophe.getLastErrorStatus();
  176. if (lastErrorStatus >= 500 && lastErrorStatus < 600) {
  177. this.eventEmitter.emit(
  178. JitsiConnectionEvents.CONNECTION_FAILED,
  179. JitsiConnectionErrors.SERVER_ERROR,
  180. errMsg ? errMsg : 'server-error');
  181. } else {
  182. this.eventEmitter.emit(
  183. JitsiConnectionEvents.CONNECTION_FAILED,
  184. JitsiConnectionErrors.CONNECTION_DROPPED_ERROR,
  185. errMsg ? errMsg : 'connection-dropped-error');
  186. }
  187. }
  188. } else if (status === Strophe.Status.AUTHFAIL) {
  189. // wrong password or username, prompt user
  190. this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
  191. JitsiConnectionErrors.PASSWORD_REQUIRED);
  192. }
  193. }
  194. /**
  195. *
  196. * @param jid
  197. * @param password
  198. */
  199. _connect(jid, password) {
  200. // connection.connect() starts the connection process.
  201. //
  202. // As the connection process proceeds, the user supplied callback will
  203. // be triggered multiple times with status updates. The callback should
  204. // take two arguments - the status code and the error condition.
  205. //
  206. // The status code will be one of the values in the Strophe.Status
  207. // constants. The error condition will be one of the conditions defined
  208. // in RFC 3920 or the condition ‘strophe-parsererror’.
  209. //
  210. // The Parameters wait, hold and route are optional and only relevant
  211. // for BOSH connections. Please see XEP 124 for a more detailed
  212. // explanation of the optional parameters.
  213. //
  214. // Connection status constants for use by the connection handler
  215. // callback.
  216. //
  217. // Status.ERROR - An error has occurred (websockets specific)
  218. // Status.CONNECTING - The connection is currently being made
  219. // Status.CONNFAIL - The connection attempt failed
  220. // Status.AUTHENTICATING - The connection is authenticating
  221. // Status.AUTHFAIL - The authentication attempt failed
  222. // Status.CONNECTED - The connection has succeeded
  223. // Status.DISCONNECTED - The connection has been terminated
  224. // Status.DISCONNECTING - The connection is currently being terminated
  225. // Status.ATTACHED - The connection has been attached
  226. this.anonymousConnectionFailed = false;
  227. this.connectionFailed = false;
  228. this.lastErrorMsg = undefined;
  229. this.connection.connect(jid, password,
  230. this.connectionHandler.bind(this, password));
  231. }
  232. /**
  233. * Attach to existing connection. Can be used for optimizations. For
  234. * example: if the connection is created on the server we can attach to it
  235. * and start using it.
  236. *
  237. * @param options {object} connecting options - rid, sid, jid and password.
  238. */
  239. attach(options) {
  240. const now = this.connectionTimes.attaching = window.performance.now();
  241. logger.log(`(TIME) Strophe Attaching\t:${now}`);
  242. this.connection.attach(options.jid, options.sid,
  243. parseInt(options.rid, 10) + 1,
  244. this.connectionHandler.bind(this, options.password));
  245. }
  246. /**
  247. *
  248. * @param jid
  249. * @param password
  250. */
  251. connect(jid, password) {
  252. this.connectParams = {
  253. jid,
  254. password
  255. };
  256. if (!jid) {
  257. let configDomain
  258. = this.options.hosts.anonymousdomain
  259. || this.options.hosts.domain;
  260. // Force authenticated domain if room is appended with '?login=true'
  261. // or if we're joining with the token
  262. if (this.options.hosts.anonymousdomain
  263. && (window.location.search.indexOf('login=true') !== -1
  264. || this.options.token)) {
  265. configDomain = this.options.hosts.domain;
  266. }
  267. // eslint-disable-next-line no-param-reassign
  268. jid = configDomain || window.location.hostname;
  269. }
  270. return this._connect(jid, password);
  271. }
  272. /**
  273. *
  274. * @param roomName
  275. * @param options
  276. */
  277. createRoom(roomName, options) {
  278. // By default MUC nickname is the resource part of the JID
  279. let mucNickname = Strophe.getNodeFromJid(this.connection.jid);
  280. let roomjid = `${roomName}@${this.options.hosts.muc}/`;
  281. const cfgNickname
  282. = options.useNicks && options.nick ? options.nick : null;
  283. if (cfgNickname) {
  284. // Use nick if it's defined
  285. mucNickname = options.nick;
  286. } else if (!this.authenticatedUser) {
  287. // node of the anonymous JID is very long - here we trim it a bit
  288. mucNickname = mucNickname.substr(0, 8);
  289. }
  290. // Constant JIDs need some random part to be appended in order to be
  291. // able to join the MUC more than once.
  292. if (this.authenticatedUser || cfgNickname !== null) {
  293. mucNickname += `-${RandomUtil.randomHexString(6)}`;
  294. }
  295. roomjid += mucNickname;
  296. return this.connection.emuc.createRoom(roomjid, null, options);
  297. }
  298. /**
  299. * Returns the logs from strophe.jingle.
  300. * @returns {Object}
  301. */
  302. getJingleLog() {
  303. const jingle = this.connection.jingle;
  304. return jingle ? jingle.getLog() : {};
  305. }
  306. /**
  307. * Returns the logs from strophe.
  308. */
  309. getXmppLog() {
  310. return (this.connection.logger || {}).log || null;
  311. }
  312. /**
  313. *
  314. */
  315. dial(...args) {
  316. this.connection.rayo.dial(...args);
  317. }
  318. /**
  319. *
  320. * @param jid
  321. * @param mute
  322. */
  323. setMute(jid, mute) {
  324. this.connection.moderate.setMute(jid, mute);
  325. }
  326. /**
  327. *
  328. * @param jid
  329. */
  330. eject(jid) {
  331. this.connection.moderate.eject(jid);
  332. }
  333. /**
  334. *
  335. */
  336. getSessions() {
  337. return this.connection.jingle.sessions;
  338. }
  339. /**
  340. * Disconnects this from the XMPP server (if this is connected).
  341. *
  342. * @param ev optionally, the event which triggered the necessity to
  343. * disconnect from the XMPP server (e.g. beforeunload, unload).
  344. */
  345. disconnect(ev) {
  346. if (this.disconnectInProgress
  347. || !this.connection
  348. || !this.connection.connected) {
  349. this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);
  350. return;
  351. }
  352. this.disconnectInProgress = true;
  353. // XXX Strophe is asynchronously sending by default. Unfortunately, that
  354. // means that there may not be enough time to send an unavailable
  355. // presence or disconnect at all. Switching Strophe to synchronous
  356. // sending is not much of an option because it may lead to a noticeable
  357. // delay in navigating away from the current location. As a compromise,
  358. // we will try to increase the chances of sending an unavailable
  359. // presence and/or disconecting within the short time span that we have
  360. // upon unloading by invoking flush() on the connection. We flush() once
  361. // before disconnect() in order to attemtp to have its unavailable
  362. // presence at the top of the send queue. We flush() once more after
  363. // disconnect() in order to attempt to have its unavailable presence
  364. // sent as soon as possible.
  365. this.connection.flush();
  366. if (ev !== null && typeof ev !== 'undefined') {
  367. const evType = ev.type;
  368. if (evType === 'beforeunload' || evType === 'unload') {
  369. // XXX Whatever we said above, synchronous sending is the best
  370. // (known) way to properly disconnect from the XMPP server.
  371. // Consequently, it may be fine to have the source code and
  372. // comment it in or out depending on whether we want to run with
  373. // it for some time.
  374. this.connection.options.sync = true;
  375. }
  376. }
  377. this.connection.disconnect();
  378. if (this.connection.options.sync !== true) {
  379. this.connection.flush();
  380. }
  381. }
  382. /**
  383. *
  384. */
  385. _initStrophePlugins() {
  386. initEmuc(this);
  387. initJingle(this, this.eventEmitter);
  388. initStropheUtil();
  389. initPing(this);
  390. initRayo();
  391. initStropheLogger();
  392. }
  393. }