Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

actions.any.ts 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import { cloneDeep } from 'lodash-es';
  2. import { IReduxState, IStore } from '../../app/types';
  3. import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
  4. import { getCurrentConference } from '../conference/functions';
  5. import { IConfigState } from '../config/reducer';
  6. import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
  7. import { isEmbedded } from '../util/embedUtils';
  8. import { parseURLParams } from '../util/parseURLParams';
  9. import {
  10. appendURLParam,
  11. getBackendSafeRoomName
  12. } from '../util/uri';
  13. import {
  14. CONNECTION_DISCONNECTED,
  15. CONNECTION_ESTABLISHED,
  16. CONNECTION_FAILED,
  17. CONNECTION_PROPERTIES_UPDATED,
  18. CONNECTION_WILL_CONNECT,
  19. SET_LOCATION_URL,
  20. SET_PREFER_VISITOR
  21. } from './actionTypes';
  22. import { JITSI_CONNECTION_URL_KEY } from './constants';
  23. import logger from './logger';
  24. import { ConnectionFailedError, IIceServers } from './types';
  25. /**
  26. * The options that will be passed to the JitsiConnection instance.
  27. */
  28. interface IOptions extends IConfigState {
  29. iceServersOverride?: IIceServers;
  30. preferVisitor?: boolean;
  31. }
  32. /**
  33. * Create an action for when the signaling connection has been lost.
  34. *
  35. * @param {JitsiConnection} connection - The {@code JitsiConnection} which
  36. * disconnected.
  37. * @private
  38. * @returns {{
  39. * type: CONNECTION_DISCONNECTED,
  40. * connection: JitsiConnection
  41. * }}
  42. */
  43. export function connectionDisconnected(connection?: Object) {
  44. return {
  45. type: CONNECTION_DISCONNECTED,
  46. connection
  47. };
  48. }
  49. /**
  50. * Create an action for when the signaling connection has been established.
  51. *
  52. * @param {JitsiConnection} connection - The {@code JitsiConnection} which was
  53. * established.
  54. * @param {number} timeEstablished - The time at which the
  55. * {@code JitsiConnection} which was established.
  56. * @public
  57. * @returns {{
  58. * type: CONNECTION_ESTABLISHED,
  59. * connection: JitsiConnection,
  60. * timeEstablished: number
  61. * }}
  62. */
  63. export function connectionEstablished(
  64. connection: Object, timeEstablished: number) {
  65. return {
  66. type: CONNECTION_ESTABLISHED,
  67. connection,
  68. timeEstablished
  69. };
  70. }
  71. /**
  72. * Create an action for when the signaling connection could not be created.
  73. *
  74. * @param {JitsiConnection} connection - The {@code JitsiConnection} which
  75. * failed.
  76. * @param {ConnectionFailedError} error - Error.
  77. * @public
  78. * @returns {{
  79. * type: CONNECTION_FAILED,
  80. * connection: JitsiConnection,
  81. * error: ConnectionFailedError
  82. * }}
  83. */
  84. export function connectionFailed(
  85. connection: Object,
  86. error: ConnectionFailedError) {
  87. const { credentials } = error;
  88. if (credentials && !Object.keys(credentials).length) {
  89. error.credentials = undefined;
  90. }
  91. return {
  92. type: CONNECTION_FAILED,
  93. connection,
  94. error
  95. };
  96. }
  97. /**
  98. * Constructs options to be passed to the constructor of {@code JitsiConnection}
  99. * based on the redux state.
  100. *
  101. * @param {Object} state - The redux state.
  102. * @returns {Object} The options to be passed to the constructor of
  103. * {@code JitsiConnection}.
  104. */
  105. export function constructOptions(state: IReduxState) {
  106. // Deep clone the options to make sure we don't modify the object in the
  107. // redux store.
  108. const options: IOptions = cloneDeep(state['features/base/config']);
  109. const { locationURL, preferVisitor } = state['features/base/connection'];
  110. const params = parseURLParams(locationURL || '');
  111. const iceServersOverride = params['iceServers.replace'];
  112. // Allow iceServersOverride only when jitsi-meet is in an iframe.
  113. if (isEmbedded() && iceServersOverride) {
  114. options.iceServersOverride = iceServersOverride;
  115. }
  116. const { bosh, preferBosh, flags } = options;
  117. let { websocket } = options;
  118. if (preferBosh) {
  119. websocket = undefined;
  120. }
  121. // WebSocket is preferred over BOSH.
  122. const serviceUrl = websocket || bosh;
  123. logger.log(`Using service URL ${serviceUrl}`);
  124. // Append room to the URL's search.
  125. const { room } = state['features/base/conference'];
  126. if (serviceUrl && room) {
  127. const roomName = getBackendSafeRoomName(room);
  128. options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName ?? '');
  129. if (options.websocketKeepAliveUrl) {
  130. options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName ?? '');
  131. }
  132. if (options.conferenceRequestUrl) {
  133. options.conferenceRequestUrl = appendURLParam(options.conferenceRequestUrl, 'room', roomName ?? '');
  134. }
  135. }
  136. if (preferVisitor) {
  137. options.preferVisitor = true;
  138. }
  139. // Enable ssrc-rewriting by default.
  140. if (typeof flags?.ssrcRewritingEnabled === 'undefined') {
  141. const { ...otherFlags } = flags ?? {};
  142. options.flags = {
  143. ...otherFlags,
  144. ssrcRewritingEnabled: true
  145. };
  146. }
  147. return options;
  148. }
  149. /**
  150. * Sets the location URL of the application, connection, conference, etc.
  151. *
  152. * @param {URL} [locationURL] - The location URL of the application,
  153. * connection, conference, etc.
  154. * @returns {{
  155. * type: SET_LOCATION_URL,
  156. * locationURL: URL
  157. * }}
  158. */
  159. export function setLocationURL(locationURL?: URL) {
  160. return {
  161. type: SET_LOCATION_URL,
  162. locationURL
  163. };
  164. }
  165. /**
  166. * To change prefer visitor in the store. Used later to decide what to request from jicofo on connection.
  167. *
  168. * @param {boolean} preferVisitor - The value to set.
  169. * @returns {{
  170. * type: SET_PREFER_VISITOR,
  171. * preferVisitor: boolean
  172. * }}
  173. */
  174. export function setPreferVisitor(preferVisitor: boolean) {
  175. return {
  176. type: SET_PREFER_VISITOR,
  177. preferVisitor
  178. };
  179. }
  180. /**
  181. * Opens new connection.
  182. *
  183. * @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
  184. * @param {string} [password] - The XMPP user's password.
  185. * @returns {Function}
  186. */
  187. export function _connectInternal(id?: string, password?: string) {
  188. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  189. const state = getState();
  190. const options = constructOptions(state);
  191. const { locationURL } = state['features/base/connection'];
  192. const { jwt } = state['features/base/jwt'];
  193. const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
  194. connection[JITSI_CONNECTION_URL_KEY] = locationURL;
  195. dispatch(_connectionWillConnect(connection));
  196. return new Promise((resolve, reject) => {
  197. connection.addEventListener(
  198. JitsiConnectionEvents.CONNECTION_DISCONNECTED,
  199. _onConnectionDisconnected);
  200. connection.addEventListener(
  201. JitsiConnectionEvents.CONNECTION_ESTABLISHED,
  202. _onConnectionEstablished);
  203. connection.addEventListener(
  204. JitsiConnectionEvents.CONNECTION_FAILED,
  205. _onConnectionFailed);
  206. connection.addEventListener(
  207. JitsiConnectionEvents.CONNECTION_REDIRECTED,
  208. _onConnectionRedirected);
  209. connection.addEventListener(
  210. JitsiConnectionEvents.PROPERTIES_UPDATED,
  211. _onPropertiesUpdate);
  212. /**
  213. * Unsubscribe the connection instance from
  214. * {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
  215. *
  216. * @returns {void}
  217. */
  218. function unsubscribe() {
  219. connection.removeEventListener(
  220. JitsiConnectionEvents.CONNECTION_DISCONNECTED, _onConnectionDisconnected);
  221. connection.removeEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed);
  222. connection.removeEventListener(JitsiConnectionEvents.PROPERTIES_UPDATED, _onPropertiesUpdate);
  223. }
  224. /**
  225. * Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
  226. * disconnected.
  227. *
  228. * @private
  229. * @returns {void}
  230. */
  231. function _onConnectionDisconnected() {
  232. unsubscribe();
  233. dispatch(connectionDisconnected(connection));
  234. resolve(connection);
  235. }
  236. /**
  237. * Rejects external promise when connection fails.
  238. *
  239. * @param {JitsiConnectionErrors} err - Connection error.
  240. * @param {string} [message] - Error message supplied by lib-jitsi-meet.
  241. * @param {Object} [credentials] - The invalid credentials that were
  242. * used to authenticate and the authentication failed.
  243. * @param {string} [credentials.jid] - The XMPP user's ID.
  244. * @param {string} [credentials.password] - The XMPP user's password.
  245. * @param {Object} details - Additional information about the error.
  246. * @private
  247. * @returns {void}
  248. */
  249. function _onConnectionFailed( // eslint-disable-line max-params
  250. err: string,
  251. message: string,
  252. credentials: any,
  253. details: Object) {
  254. unsubscribe();
  255. dispatch(connectionFailed(connection, {
  256. credentials,
  257. details,
  258. name: err,
  259. message
  260. }));
  261. reject(err);
  262. }
  263. /**
  264. * Resolves external promise when connection is established.
  265. *
  266. * @private
  267. * @returns {void}
  268. */
  269. function _onConnectionEstablished() {
  270. connection.removeEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED, _onConnectionEstablished);
  271. dispatch(connectionEstablished(connection, Date.now()));
  272. resolve(connection);
  273. }
  274. /**
  275. * Connection was redirected.
  276. *
  277. * @param {string|undefined} vnode - The vnode to connect to.
  278. * @param {string} focusJid - The focus jid to use.
  279. * @param {string|undefined} username - The username to use when joining. This is after promotion from
  280. * visitor to main participant.
  281. * @private
  282. * @returns {void}
  283. */
  284. function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
  285. connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
  286. dispatch(redirect(vnode, focusJid, username));
  287. }
  288. /**
  289. * Connection properties were updated.
  290. *
  291. * @param {Object} properties - The properties which were updated.
  292. * @private
  293. * @returns {void}
  294. */
  295. function _onPropertiesUpdate(properties: object) {
  296. dispatch(_propertiesUpdate(properties));
  297. }
  298. // in case of configured http url for conference request we need the room name
  299. const name = getBackendSafeRoomName(state['features/base/conference'].room);
  300. connection.connect({
  301. id,
  302. password,
  303. name
  304. });
  305. });
  306. };
  307. }
  308. /**
  309. * Create an action for when a connection will connect.
  310. *
  311. * @param {JitsiConnection} connection - The {@code JitsiConnection} which will
  312. * connect.
  313. * @private
  314. * @returns {{
  315. * type: CONNECTION_WILL_CONNECT,
  316. * connection: JitsiConnection
  317. * }}
  318. */
  319. function _connectionWillConnect(connection: Object) {
  320. return {
  321. type: CONNECTION_WILL_CONNECT,
  322. connection
  323. };
  324. }
  325. /**
  326. * Create an action for when connection properties are updated.
  327. *
  328. * @param {Object} properties - The properties which were updated.
  329. * @private
  330. * @returns {{
  331. * type: CONNECTION_PROPERTIES_UPDATED,
  332. * properties: Object
  333. * }}
  334. */
  335. function _propertiesUpdate(properties: object) {
  336. return {
  337. type: CONNECTION_PROPERTIES_UPDATED,
  338. properties
  339. };
  340. }
  341. /**
  342. * Closes connection.
  343. *
  344. * @param {boolean} isRedirect - Indicates if the action has been dispatched as part of visitor promotion.
  345. *
  346. * @returns {Function}
  347. */
  348. export function disconnect(isRedirect?: boolean) {
  349. return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
  350. const state = getState();
  351. // The conference we have already joined or are joining.
  352. const conference_ = getCurrentConference(state);
  353. // Promise which completes when the conference has been left and the
  354. // connection has been disconnected.
  355. let promise;
  356. // Leave the conference.
  357. if (conference_) {
  358. // In a fashion similar to JitsiConference's CONFERENCE_LEFT event
  359. // (and the respective Redux action) which is fired after the
  360. // conference has been left, notify the application about the
  361. // intention to leave the conference.
  362. dispatch(conferenceWillLeave(conference_, isRedirect));
  363. promise
  364. = conference_.leave()
  365. .catch((error: Error) => {
  366. logger.warn(
  367. 'JitsiConference.leave() rejected with:',
  368. error);
  369. // The library lib-jitsi-meet failed to make the
  370. // JitsiConference leave. Which may be because
  371. // JitsiConference thinks it has already left.
  372. // Regardless of the failure reason, continue in
  373. // jitsi-meet as if the leave has succeeded.
  374. dispatch(conferenceLeft(conference_));
  375. });
  376. } else {
  377. promise = Promise.resolve();
  378. }
  379. // Disconnect the connection.
  380. const { connecting, connection } = state['features/base/connection'];
  381. // The connection we have already connected or are connecting.
  382. const connection_ = connection || connecting;
  383. if (connection_) {
  384. promise = promise.then(() => connection_.disconnect());
  385. } else {
  386. logger.info('No connection found while disconnecting.');
  387. }
  388. return promise;
  389. };
  390. }