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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // @flow
  2. import { getLocalParticipant, PARTICIPANT_ROLE } from '../base/participants';
  3. import { doGetJSON } from '../base/util';
  4. declare var $: Function;
  5. declare var interfaceConfig: Object;
  6. const logger = require('jitsi-meet-logger').getLogger(__filename);
  7. /**
  8. * Sends an ajax request to check if the phone number can be called.
  9. *
  10. * @param {string} dialNumber - The dial number to check for validity.
  11. * @param {string} dialOutAuthUrl - The endpoint to use for checking validity.
  12. * @returns {Promise} - The promise created by the request.
  13. */
  14. export function checkDialNumber(
  15. dialNumber: string,
  16. dialOutAuthUrl: string
  17. ): Promise<Object> {
  18. if (!dialOutAuthUrl) {
  19. // no auth url, let's say it is valid
  20. const response = {
  21. allow: true,
  22. phone: `+${dialNumber}`
  23. };
  24. return Promise.resolve(response);
  25. }
  26. const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
  27. return new Promise((resolve, reject) => {
  28. $.getJSON(fullUrl)
  29. .then(resolve)
  30. .catch(reject);
  31. });
  32. }
  33. /**
  34. * Sends a GET request to obtain the conference ID necessary for identifying
  35. * which conference to join after diaing the dial-in service.
  36. *
  37. * @param {string} baseUrl - The url for obtaining the conference ID (pin) for
  38. * dialing into a conference.
  39. * @param {string} roomName - The conference name to find the associated
  40. * conference ID.
  41. * @param {string} mucURL - In which MUC the conference exists.
  42. * @returns {Promise} - The promise created by the request.
  43. */
  44. export function getDialInConferenceID(
  45. baseUrl: string,
  46. roomName: string,
  47. mucURL: string
  48. ): Promise<Object> {
  49. const conferenceIDURL = `${baseUrl}?conference=${roomName}@${mucURL}`;
  50. return doGetJSON(conferenceIDURL);
  51. }
  52. /**
  53. * Sends a GET request for phone numbers used to dial into a conference.
  54. *
  55. * @param {string} url - The service that returns confernce dial-in numbers.
  56. * @returns {Promise} - The promise created by the request. The returned numbers
  57. * may be an array of numbers or an object with countries as keys and arrays of
  58. * phone number strings.
  59. */
  60. export function getDialInNumbers(url: string): Promise<*> {
  61. return doGetJSON(url);
  62. }
  63. /**
  64. * Removes all non-numeric characters from a string.
  65. *
  66. * @param {string} text - The string from which to remove all characters
  67. * except numbers.
  68. * @private
  69. * @returns {string} A string with only numbers.
  70. */
  71. export function getDigitsOnly(text: string = ''): string {
  72. return text.replace(/\D/g, '');
  73. }
  74. /**
  75. * Type of the options to use when sending a search query.
  76. */
  77. export type GetInviteResultsOptions = {
  78. /**
  79. * The endpoint to use for checking phone number validity.
  80. */
  81. dialOutAuthUrl: string,
  82. /**
  83. * Whether or not to search for people.
  84. */
  85. addPeopleEnabled: boolean,
  86. /**
  87. * Whether or not to check phone numbers.
  88. */
  89. dialOutEnabled: boolean,
  90. /**
  91. * Array with the query types that will be executed -
  92. * "conferenceRooms" | "user" | "room".
  93. */
  94. peopleSearchQueryTypes: Array<string>,
  95. /**
  96. * The url to query for people.
  97. */
  98. peopleSearchUrl: string,
  99. /**
  100. * The jwt token to pass to the search service.
  101. */
  102. jwt: string
  103. };
  104. /**
  105. * Combines directory search with phone number validation to produce a single
  106. * set of invite search results.
  107. *
  108. * @param {string} query - Text to search.
  109. * @param {GetInviteResultsOptions} options - Options to use when searching.
  110. * @returns {Promise<*>}
  111. */
  112. export function getInviteResultsForQuery(
  113. query: string,
  114. options: GetInviteResultsOptions
  115. ): Promise<*> {
  116. const text = query.trim();
  117. const {
  118. dialOutAuthUrl,
  119. addPeopleEnabled,
  120. dialOutEnabled,
  121. peopleSearchQueryTypes,
  122. peopleSearchUrl,
  123. jwt
  124. } = options;
  125. let peopleSearchPromise;
  126. if (addPeopleEnabled && text) {
  127. peopleSearchPromise = searchDirectory(
  128. peopleSearchUrl,
  129. jwt,
  130. text,
  131. peopleSearchQueryTypes);
  132. } else {
  133. peopleSearchPromise = Promise.resolve([]);
  134. }
  135. const hasCountryCode = text.startsWith('+');
  136. let phoneNumberPromise;
  137. if (dialOutEnabled && isMaybeAPhoneNumber(text)) {
  138. let numberToVerify = text;
  139. // When the number to verify does not start with a +, we assume no
  140. // proper country code has been entered. In such a case, prepend 1
  141. // for the country code. The service currently takes care of
  142. // prepending the +.
  143. if (!hasCountryCode && !text.startsWith('1')) {
  144. numberToVerify = `1${numberToVerify}`;
  145. }
  146. // The validation service works properly when the query is digits
  147. // only so ensure only digits get sent.
  148. numberToVerify = getDigitsOnly(numberToVerify);
  149. phoneNumberPromise
  150. = checkDialNumber(numberToVerify, dialOutAuthUrl);
  151. } else {
  152. phoneNumberPromise = Promise.resolve({});
  153. }
  154. return Promise.all([ peopleSearchPromise, phoneNumberPromise ])
  155. .then(([ peopleResults, phoneResults ]) => {
  156. const results = [
  157. ...peopleResults
  158. ];
  159. /**
  160. * This check for phone results is for the day the call to
  161. * searching people might return phone results as well. When
  162. * that day comes this check will make it so the server checks
  163. * are honored and the local appending of the number is not
  164. * done. The local appending of the phone number can then be
  165. * cleaned up when convenient.
  166. */
  167. const hasPhoneResult = peopleResults.find(
  168. result => result.type === 'phone');
  169. if (!hasPhoneResult
  170. && typeof phoneResults.allow === 'boolean') {
  171. results.push({
  172. allowed: phoneResults.allow,
  173. country: phoneResults.country,
  174. type: 'phone',
  175. number: phoneResults.phone,
  176. originalEntry: text,
  177. showCountryCodeReminder: !hasCountryCode
  178. });
  179. }
  180. return results;
  181. });
  182. }
  183. /**
  184. * Sends a post request to an invite service.
  185. *
  186. * @param {string} inviteServiceUrl - The invite service that generates the
  187. * invitation.
  188. * @param {string} inviteUrl - The url to the conference.
  189. * @param {string} jwt - The jwt token to pass to the search service.
  190. * @param {Immutable.List} inviteItems - The list of the "user" or "room"
  191. * type items to invite.
  192. * @returns {Promise} - The promise created by the request.
  193. */
  194. export function invitePeopleAndChatRooms( // eslint-disable-line max-params
  195. inviteServiceUrl: string,
  196. inviteUrl: string,
  197. jwt: string,
  198. inviteItems: Array<Object>
  199. ): Promise<void> {
  200. if (!inviteItems || inviteItems.length === 0) {
  201. return Promise.resolve();
  202. }
  203. return new Promise((resolve, reject) => {
  204. $.post(
  205. `${inviteServiceUrl}?token=${jwt}`,
  206. JSON.stringify({
  207. 'invited': inviteItems,
  208. 'url': inviteUrl
  209. }),
  210. resolve,
  211. 'json')
  212. .fail((jqxhr, textStatus, error) => reject(error));
  213. });
  214. }
  215. /**
  216. * Determines if adding people is currently enabled.
  217. *
  218. * @param {boolean} state - Current state.
  219. * @returns {boolean} Indication of whether adding people is currently enabled.
  220. */
  221. export function isAddPeopleEnabled(state: Object): boolean {
  222. const { isGuest } = state['features/base/jwt'];
  223. if (!isGuest) {
  224. // XXX The mobile/react-native app is capable of disabling the
  225. // adding/inviting of people in the current conference. Anyway, the
  226. // Web/React app does not have that capability so default appropriately.
  227. const { app } = state['features/app'];
  228. const addPeopleEnabled = app && app.props.addPeopleEnabled;
  229. return (
  230. (typeof addPeopleEnabled === 'undefined')
  231. || Boolean(addPeopleEnabled));
  232. }
  233. return false;
  234. }
  235. /**
  236. * Determines if dial out is currently enabled or not.
  237. *
  238. * @param {boolean} state - Current state.
  239. * @returns {boolean} Indication of whether dial out is currently enabled.
  240. */
  241. export function isDialOutEnabled(state: Object): boolean {
  242. const { conference } = state['features/base/conference'];
  243. const { isGuest } = state['features/base/jwt'];
  244. const { enableUserRolesBasedOnToken } = state['features/base/config'];
  245. const participant = getLocalParticipant(state);
  246. return participant && participant.role === PARTICIPANT_ROLE.MODERATOR
  247. && conference && conference.isSIPCallingSupported()
  248. && (!enableUserRolesBasedOnToken || !isGuest);
  249. }
  250. /**
  251. * Checks whether a string looks like it could be for a phone number.
  252. *
  253. * @param {string} text - The text to check whether or not it could be a
  254. * phone number.
  255. * @private
  256. * @returns {boolean} True if the string looks like it could be a phone
  257. * number.
  258. */
  259. function isMaybeAPhoneNumber(text: string): boolean {
  260. if (!isPhoneNumberRegex().test(text)) {
  261. return false;
  262. }
  263. const digits = getDigitsOnly(text);
  264. return Boolean(digits.length);
  265. }
  266. /**
  267. * RegExp to use to determine if some text might be a phone number.
  268. *
  269. * @returns {RegExp}
  270. */
  271. function isPhoneNumberRegex(): RegExp {
  272. let regexString = '^[0-9+()-\\s]*$';
  273. if (typeof interfaceConfig !== 'undefined') {
  274. regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
  275. }
  276. return new RegExp(regexString);
  277. }
  278. /**
  279. * Sends an ajax request to a directory service.
  280. *
  281. * @param {string} serviceUrl - The service to query.
  282. * @param {string} jwt - The jwt token to pass to the search service.
  283. * @param {string} text - Text to search.
  284. * @param {Array<string>} queryTypes - Array with the query types that will be
  285. * executed - "conferenceRooms" | "user" | "room".
  286. * @returns {Promise} - The promise created by the request.
  287. */
  288. export function searchDirectory( // eslint-disable-line max-params
  289. serviceUrl: string,
  290. jwt: string,
  291. text: string,
  292. queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
  293. ): Promise<Array<Object>> {
  294. const query = encodeURIComponent(text);
  295. const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
  296. return fetch(`${serviceUrl}?query=${query}&queryTypes=${
  297. queryTypesString}&jwt=${jwt}`)
  298. .then(response => {
  299. const jsonify = response.json();
  300. if (response.ok) {
  301. return jsonify;
  302. }
  303. return jsonify
  304. .then(result => Promise.reject(result));
  305. })
  306. .catch(error => {
  307. logger.error(
  308. 'Error searching directory:', error);
  309. return Promise.reject(error);
  310. });
  311. }
  312. /**
  313. * Helper for determining how many of each type of user is being invited.
  314. * Used for logging and sending analytics related to invites.
  315. *
  316. * @param {Array} inviteItems - An array with the invite items, as created
  317. * in {@link _parseQueryResults}.
  318. * @private
  319. * @returns {Object} An object with keys as user types and values as the
  320. * number of invites for that type.
  321. */
  322. export function getInviteTypeCounts(inviteItems: Array<Object> = []) {
  323. const inviteTypeCounts = {};
  324. inviteItems.forEach(({ type }) => {
  325. if (!inviteTypeCounts[type]) {
  326. inviteTypeCounts[type] = 0;
  327. }
  328. inviteTypeCounts[type]++;
  329. });
  330. return inviteTypeCounts;
  331. }