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.

connection.js 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /* global APP, JitsiMeetJS, config */
  2. import { jitsiLocalStorage } from '@jitsi/js-utils';
  3. import Logger from 'jitsi-meet-logger';
  4. import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
  5. import { LoginDialog } from './react/features/authentication/components';
  6. import { isTokenAuthEnabled } from './react/features/authentication/functions';
  7. import {
  8. connectionEstablished,
  9. connectionFailed
  10. } from './react/features/base/connection/actions';
  11. import { openDialog } from './react/features/base/dialog/actions';
  12. import { setJWT } from './react/features/base/jwt';
  13. import {
  14. isFatalJitsiConnectionError,
  15. JitsiConnectionErrors,
  16. JitsiConnectionEvents
  17. } from './react/features/base/lib-jitsi-meet';
  18. import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
  19. import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
  20. const logger = Logger.getLogger(__filename);
  21. /**
  22. * The feature announced so we can distinguish jibri participants.
  23. *
  24. * @type {string}
  25. */
  26. export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
  27. /**
  28. * Checks if we have data to use attach instead of connect. If we have the data
  29. * executes attach otherwise check if we have to wait for the data. If we have
  30. * to wait for the attach data we are setting handler to APP.connect.handler
  31. * which is going to be called when the attach data is received otherwise
  32. * executes connect.
  33. *
  34. * @param {string} [id] user id
  35. * @param {string} [password] password
  36. * @param {string} [roomName] the name of the conference.
  37. */
  38. function checkForAttachParametersAndConnect(id, password, connection) {
  39. if (window.XMPPAttachInfo) {
  40. APP.connect.status = 'connecting';
  41. // When connection optimization is not deployed or enabled the default
  42. // value will be window.XMPPAttachInfo.status = "error"
  43. // If the connection optimization is deployed and enabled and there is
  44. // a failure the value will be window.XMPPAttachInfo.status = "error"
  45. if (window.XMPPAttachInfo.status === 'error') {
  46. connection.connect({
  47. id,
  48. password
  49. });
  50. return;
  51. }
  52. const attachOptions = window.XMPPAttachInfo.data;
  53. if (attachOptions) {
  54. connection.attach(attachOptions);
  55. delete window.XMPPAttachInfo.data;
  56. } else {
  57. connection.connect({
  58. id,
  59. password
  60. });
  61. }
  62. } else {
  63. APP.connect.status = 'ready';
  64. APP.connect.handler
  65. = checkForAttachParametersAndConnect.bind(
  66. null,
  67. id, password, connection);
  68. }
  69. }
  70. /**
  71. * Try to open connection using provided credentials.
  72. * @param {string} [id]
  73. * @param {string} [password]
  74. * @param {string} [roomName]
  75. * @returns {Promise<JitsiConnection>} connection if
  76. * everything is ok, else error.
  77. */
  78. export async function connect(id, password, roomName) {
  79. const connectionConfig = Object.assign({}, config);
  80. const state = APP.store.getState();
  81. let { jwt } = state['features/base/jwt'];
  82. if (!jwt && isVpaasMeeting(state)) {
  83. jwt = await getJaasJWT(state);
  84. APP.store.dispatch(setJWT(jwt));
  85. }
  86. // Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
  87. // that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
  88. let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
  89. serviceUrl += `?room=${roomName}`;
  90. // FIXME Remove deprecated 'bosh' option assignment at some point(LJM will be accepting only 'serviceUrl' option
  91. // in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
  92. connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
  93. if (connectionConfig.websocketKeepAliveUrl) {
  94. connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
  95. }
  96. const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
  97. if (config.iAmRecorder) {
  98. connection.addFeature(DISCO_JIBRI_FEATURE);
  99. }
  100. return new Promise((resolve, reject) => {
  101. connection.addEventListener(
  102. JitsiConnectionEvents.CONNECTION_ESTABLISHED,
  103. handleConnectionEstablished);
  104. connection.addEventListener(
  105. JitsiConnectionEvents.CONNECTION_FAILED,
  106. handleConnectionFailed);
  107. connection.addEventListener(
  108. JitsiConnectionEvents.CONNECTION_FAILED,
  109. connectionFailedHandler);
  110. connection.addEventListener(
  111. JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
  112. displayNameRequiredHandler
  113. );
  114. /* eslint-disable max-params */
  115. /**
  116. *
  117. */
  118. function connectionFailedHandler(error, message, credentials, details) {
  119. /* eslint-enable max-params */
  120. APP.store.dispatch(
  121. connectionFailed(
  122. connection, {
  123. credentials,
  124. details,
  125. message,
  126. name: error
  127. }));
  128. if (isFatalJitsiConnectionError(error)) {
  129. connection.removeEventListener(
  130. JitsiConnectionEvents.CONNECTION_FAILED,
  131. connectionFailedHandler);
  132. }
  133. }
  134. /**
  135. *
  136. */
  137. function unsubscribe() {
  138. connection.removeEventListener(
  139. JitsiConnectionEvents.CONNECTION_ESTABLISHED,
  140. handleConnectionEstablished);
  141. connection.removeEventListener(
  142. JitsiConnectionEvents.CONNECTION_FAILED,
  143. handleConnectionFailed);
  144. }
  145. /**
  146. *
  147. */
  148. function handleConnectionEstablished() {
  149. APP.store.dispatch(connectionEstablished(connection, Date.now()));
  150. unsubscribe();
  151. resolve(connection);
  152. }
  153. /**
  154. *
  155. */
  156. function handleConnectionFailed(err) {
  157. unsubscribe();
  158. logger.error('CONNECTION FAILED:', err);
  159. reject(err);
  160. }
  161. /**
  162. * Marks the display name for the prejoin screen as required.
  163. * This can happen if a user tries to join a room with lobby enabled.
  164. */
  165. function displayNameRequiredHandler() {
  166. APP.store.dispatch(setPrejoinDisplayNameRequired());
  167. }
  168. checkForAttachParametersAndConnect(id, password, connection);
  169. });
  170. }
  171. /**
  172. * Open JitsiConnection using provided credentials.
  173. * If retry option is true it will show auth dialog on PASSWORD_REQUIRED error.
  174. *
  175. * @param {object} options
  176. * @param {string} [options.id]
  177. * @param {string} [options.password]
  178. * @param {string} [options.roomName]
  179. * @param {boolean} [retry] if we should show auth dialog
  180. * on PASSWORD_REQUIRED error.
  181. *
  182. * @returns {Promise<JitsiConnection>}
  183. */
  184. export function openConnection({ id, password, retry, roomName }) {
  185. const usernameOverride
  186. = jitsiLocalStorage.getItem('xmpp_username_override');
  187. const passwordOverride
  188. = jitsiLocalStorage.getItem('xmpp_password_override');
  189. if (usernameOverride && usernameOverride.length > 0) {
  190. id = usernameOverride; // eslint-disable-line no-param-reassign
  191. }
  192. if (passwordOverride && passwordOverride.length > 0) {
  193. password = passwordOverride; // eslint-disable-line no-param-reassign
  194. }
  195. return connect(id, password, roomName).catch(err => {
  196. if (retry) {
  197. const { jwt } = APP.store.getState()['features/base/jwt'];
  198. if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
  199. return requestAuth(roomName);
  200. }
  201. }
  202. throw err;
  203. });
  204. }
  205. /**
  206. * Show Authentication Dialog and try to connect with new credentials.
  207. * If failed to connect because of PASSWORD_REQUIRED error
  208. * then ask for password again.
  209. * @param {string} [roomName] name of the conference room
  210. *
  211. * @returns {Promise<JitsiConnection>}
  212. */
  213. function requestAuth(roomName) {
  214. const config = APP.store.getState()['features/base/config'];
  215. if (isTokenAuthEnabled(config)) {
  216. // This Promise never resolves as user gets redirected to another URL
  217. return new Promise(() => redirectToTokenAuthService(roomName));
  218. }
  219. return new Promise(resolve => {
  220. const onSuccess = connection => {
  221. resolve(connection);
  222. };
  223. APP.store.dispatch(
  224. openDialog(LoginDialog, { onSuccess,
  225. roomName })
  226. );
  227. });
  228. }