Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

actions.any.ts 12KB

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