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.

actions.any.ts 12KB

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