You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

actions.native.js 11KB

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