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.

functions.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /* @flow */
  2. import JSSHA from 'jssha';
  3. import _ from 'lodash';
  4. import parseURLParams from './parseURLParams';
  5. declare var $: Object;
  6. /**
  7. * The config keys to ignore because, for example, their values identify scripts
  8. * and it is not desireable to inject these through URL params.
  9. *
  10. * @private
  11. * @type Array
  12. */
  13. const _KEYS_TO_IGNORE = [
  14. 'analyticsScriptUrls',
  15. 'callStatsCustomScriptUrl'
  16. ];
  17. const logger = require('jitsi-meet-logger').getLogger(__filename);
  18. // XXX The functions getRoomName and parseURLParams are split out of
  19. // functions.js because they are bundled in both app.bundle and
  20. // do_external_connect, webpack 1 does not support tree shaking, and we don't
  21. // want all functions to be bundled in do_external_connect.
  22. export { default as getRoomName } from './getRoomName';
  23. export { parseURLParams };
  24. /* eslint-disable no-shadow */
  25. /**
  26. * Looks for a list of possible BOSH addresses in {@code config.boshList} and
  27. * sets the value of {@code config.bosh} based on that list and
  28. * {@code roomName}.
  29. *
  30. * @param {Object} config - The configuration object.
  31. * @param {string} roomName - The name of the room/conference.
  32. * @returns {void}
  33. */
  34. export function chooseBOSHAddress(config: Object, roomName: string) {
  35. if (!roomName) {
  36. return;
  37. }
  38. const { boshList } = config;
  39. if (!boshList || !Array.isArray(boshList) || !boshList.length) {
  40. return;
  41. }
  42. // This implements the actual choice of an entry in the list based on
  43. // roomName. Please consider the implications for existing deployments
  44. // before introducing changes.
  45. const hash = (new JSSHA(roomName, 'TEXT')).getHash('SHA-1', 'HEX');
  46. const n = parseInt(hash.substr(-6), 16);
  47. let idx = n % boshList.length;
  48. config.bosh = boshList[idx];
  49. logger.log(`Setting config.bosh to ${config.bosh} (idx=${idx})`);
  50. const { boshAttemptFirstList } = config;
  51. if (boshAttemptFirstList
  52. && Array.isArray(boshAttemptFirstList)
  53. && boshAttemptFirstList.length > 0) {
  54. idx = n % boshAttemptFirstList.length;
  55. const attemptFirstAddress = boshAttemptFirstList[idx];
  56. if (attemptFirstAddress === config.bosh) {
  57. logger.log('Not setting config.boshAttemptFirst, address matches.');
  58. } else {
  59. config.boshAttemptFirst = attemptFirstAddress;
  60. logger.log(
  61. `Setting config.boshAttemptFirst=${attemptFirstAddress} (idx=${
  62. idx})`);
  63. }
  64. }
  65. }
  66. /* eslint-enable no-shadow */
  67. /**
  68. * Sends HTTP POST request to specified {@code endpoint}. In request the name
  69. * of the room is included in JSON format:
  70. * {
  71. * "rooomName": "someroom12345"
  72. * }.
  73. *
  74. * @param {string} endpoint - The name of HTTP endpoint to which to send
  75. * the HTTP POST request.
  76. * @param {string} roomName - The name of the conference room for which config
  77. * is requested.
  78. * @param {Function} complete - The callback to invoke upon success or failure.
  79. * @returns {void}
  80. */
  81. export function obtainConfig(
  82. endpoint: string,
  83. roomName: string,
  84. complete: Function) {
  85. logger.info(`Send config request to ${endpoint} for room: ${roomName}`);
  86. $.ajax(
  87. endpoint,
  88. {
  89. contentType: 'application/json',
  90. data: JSON.stringify({ roomName }),
  91. dataType: 'json',
  92. method: 'POST',
  93. error(jqXHR, textStatus, errorThrown) {
  94. logger.error('Get config error: ', jqXHR, errorThrown);
  95. complete(false, `Get config response status: ${textStatus}`);
  96. },
  97. success(data) {
  98. const { config, interfaceConfig, loggingConfig } = window;
  99. try {
  100. overrideConfigJSON(
  101. config, interfaceConfig, loggingConfig,
  102. data);
  103. complete(true);
  104. } catch (e) {
  105. logger.error('Parse config error: ', e);
  106. complete(false, e);
  107. }
  108. }
  109. }
  110. );
  111. }
  112. /* eslint-disable max-params, no-shadow */
  113. /**
  114. * Overrides JSON properties in {@code config} and
  115. * {@code interfaceConfig} Objects with the values from {@code newConfig}.
  116. *
  117. * @param {Object} config - The config Object in which we'll be overriding
  118. * properties.
  119. * @param {Object} interfaceConfig - The interfaceConfig Object in which we'll
  120. * be overriding properties.
  121. * @param {Object} loggingConfig - The loggingConfig Object in which we'll be
  122. * overriding properties.
  123. * @param {Object} json - Object containing configuration properties.
  124. * Destination object is selected based on root property name:
  125. * {
  126. * config: {
  127. * // config.js properties here
  128. * },
  129. * interfaceConfig: {
  130. * // interface_config.js properties here
  131. * },
  132. * loggingConfig: {
  133. * // logging_config.js properties here
  134. * }
  135. * }.
  136. * @returns {void}
  137. */
  138. export function overrideConfigJSON(
  139. config: Object, interfaceConfig: Object, loggingConfig: Object,
  140. json: Object) {
  141. for (const configName of Object.keys(json)) {
  142. let configObj;
  143. if (configName === 'config') {
  144. configObj = config;
  145. } else if (configName === 'interfaceConfig') {
  146. configObj = interfaceConfig;
  147. } else if (configName === 'loggingConfig') {
  148. configObj = loggingConfig;
  149. }
  150. if (configObj) {
  151. const configJSON = json[configName];
  152. if (!_.isEmpty(configJSON)) {
  153. logger.info(
  154. `Extending ${configName} with: ${
  155. JSON.stringify(configJSON)}`);
  156. // eslint-disable-next-line arrow-body-style
  157. _.mergeWith(configObj, configJSON, (oldValue, newValue) => {
  158. // XXX We don't want to merge the arrays, we want to
  159. // overwrite them.
  160. return Array.isArray(oldValue) ? newValue : undefined;
  161. });
  162. }
  163. }
  164. }
  165. }
  166. /* eslint-enable max-params, no-shadow */
  167. /**
  168. * Converts 'URL_PARAMS' to JSON object.
  169. * We have:
  170. * {
  171. * "config.disableAudioLevels": false,
  172. * "config.channelLastN": -1,
  173. * "interfaceConfig.APP_NAME": "Jitsi Meet"
  174. * }.
  175. * We want to have:
  176. * {
  177. * "config": {
  178. * "disableAudioLevels": false,
  179. * "channelLastN": -1
  180. * },
  181. * interfaceConfig: {
  182. * "APP_NAME": "Jitsi Meet"
  183. * }
  184. * }.
  185. *
  186. * @returns {void}
  187. */
  188. export function setConfigFromURLParams() {
  189. const params = parseURLParams(window.location);
  190. const { config, interfaceConfig, loggingConfig } = window;
  191. const json = {};
  192. // TODO We're still in the middle ground between old Web with config,
  193. // interfaceConfig, and loggingConfig used via global variables and new Web
  194. // and mobile reading the respective values from the redux store. On React
  195. // Native there's no interfaceConfig at all yet and loggingConfig is not
  196. // loaded but there's a default value in the redux store.
  197. config && (json.config = {});
  198. interfaceConfig && (json.interfaceConfig = {});
  199. loggingConfig && (json.loggingConfig = {});
  200. for (const param of Object.keys(params)) {
  201. let base = json;
  202. const names = param.split('.');
  203. const last = names.pop();
  204. // Prevent passing some parameters which can inject scripts.
  205. if (_KEYS_TO_IGNORE.indexOf(last) !== -1) {
  206. // eslint-disable-next-line no-continue
  207. continue;
  208. }
  209. for (const name of names) {
  210. base = base[name] = base[name] || {};
  211. }
  212. base[last] = params[param];
  213. }
  214. overrideConfigJSON(config, interfaceConfig, loggingConfig, json);
  215. }