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

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