diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index 9db771e71..92d83d18e 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -1,5 +1,6 @@ import EventEmitter from 'events'; +import { urlObjectToString } from '../../../react/features/base/util'; import { PostMessageTransportBackend, Transport @@ -58,28 +59,6 @@ function changeParticipantNumber(APIInstance, number) { APIInstance._numberOfParticipants += number; } -/** - * Generates array with URL params based on the passed config object that will - * be used for the Jitsi Meet URL generation. - * - * @param {Object} config - The config object. - * @returns {Array} The array with URL param strings. - */ -function configToURLParamsArray(config = {}) { - const params = []; - - for (const key in config) { // eslint-disable-line guard-for-in - try { - params.push( - `${key}=${encodeURIComponent(JSON.stringify(config[key]))}`); - } catch (e) { - console.warn(`Error encoding ${key}: ${e}`); - } - } - - return params; -} - /** * Generates the URL for the iframe. * @@ -92,42 +71,17 @@ function configToURLParamsArray(config = {}) { * configuration options defined in interface_config.js to be overridden. * @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for * authentication. - * @param {boolean} [options.noSsl] - If the value is true https won't be used. + * @param {boolean} [options.noSSL] - If the value is true https won't be used. * @param {string} [options.roomName] - The name of the room to join. * @returns {string} The URL. */ function generateURL(domain, options = {}) { - const { - configOverwrite, - interfaceConfigOverwrite, - jwt, - noSSL, - roomName - } = options; - - let url = `${noSSL ? 'http' : 'https'}://${domain}/${roomName || ''}`; - - if (jwt) { - url += `?jwt=${jwt}`; - } - - url += `#jitsi_meet_external_api_id=${id}`; - - const configURLParams = configToURLParamsArray(configOverwrite); - - if (configURLParams.length) { - url += `&config.${configURLParams.join('&config.')}`; - } - - const interfaceConfigURLParams - = configToURLParamsArray(interfaceConfigOverwrite); - - if (interfaceConfigURLParams.length) { - url += `&interfaceConfig.${ - interfaceConfigURLParams.join('&interfaceConfig.')}`; - } - - return url; + return urlObjectToString({ + ...options, + url: + `${options.noSSL ? 'http' : 'https'}://${domain + }/#jitsi_meet_external_api_id=${id}` + }); } /** diff --git a/react/features/app/actions.js b/react/features/app/actions.js index 7bf95a82c..5aa793472 100644 --- a/react/features/app/actions.js +++ b/react/features/app/actions.js @@ -90,17 +90,7 @@ function _appNavigateToMandatoryLocation( * @returns {void} */ function dispatchSetLocationURL() { - dispatch( - setLocationURL( - new URL( - (newLocation.protocol || 'https:') - - // TODO userinfo - - + newLocation.host - + (newLocation.pathname || '/') - + newLocation.search - + newLocation.hash))); + dispatch(setLocationURL(new URL(newLocation.toString()))); } /** @@ -147,9 +137,7 @@ function _appNavigateToOptionalLocation( } } - if (!location.protocol) { - location.protocol = 'https:'; - } + location.protocol || (location.protocol = 'https:'); _appNavigateToMandatoryLocation(dispatch, getState, location); } @@ -213,5 +201,7 @@ function _loadConfig(location: Object) { // TDOO userinfo - return loadConfig(protocol + location.host + (location.contextRoot || '/')); + return ( + loadConfig( + `${protocol}//${location.host}${location.contextRoot || '/'}`)); } diff --git a/react/features/base/config/parseURLParams.js b/react/features/base/config/parseURLParams.js index afc43f090..6603bec26 100644 --- a/react/features/base/config/parseURLParams.js +++ b/react/features/base/config/parseURLParams.js @@ -1,13 +1,14 @@ /* @flow */ /** - * Parses the parameters from the URL and returns them as a JS object. + * Parses the query/search or fragment/hash parameters out of a specific URL and + * returns them as a JS object. * * @param {string} url - The URL to parse. - * @param {boolean} dontParse - If false or undefined some transformations - * (for parsing the value as JSON) are going to be executed. - * @param {string} source - Values - "hash"/"search" if "search" the parameters - * will parsed from location.search otherwise from location.hash. + * @param {boolean} dontParse - If falsy, some transformations (for parsing the + * value as JSON) will be executed. + * @param {string} source - If {@code 'search'}, the parameters will parsed out + * of {@code url.search}; otherwise, out of {@code url.hash}. * @returns {Object} */ export default function parseURLParams( diff --git a/react/features/base/util/uri.js b/react/features/base/util/uri.js index 10047d78e..922764203 100644 --- a/react/features/base/util/uri.js +++ b/react/features/base/util/uri.js @@ -1,5 +1,3 @@ -/* @flow */ - /** * The {@link RegExp} pattern of the authority of a URI. * @@ -127,6 +125,30 @@ export function getLocationContextRoot(location: Object) { : pathname.substring(0, contextRootEndIndex + 1)); } +/** + * Constructs a new {@code Array} with URL parameter {@code String}s out of a + * specific {@code Object}. + * + * @param {Object} obj - The {@code Object} to turn into URL parameter + * {@code String}s. + * @returns {Array} The {@code Array} with URL parameter {@code String}s + * constructed out of the specified {@code obj}. + */ +function _objectToURLParamsArray(obj = {}) { + const params = []; + + for (const key in obj) { // eslint-disable-line guard-for-in + try { + params.push( + `${key}=${encodeURIComponent(JSON.stringify(obj[key]))}`); + } catch (e) { + console.warn(`Error encoding ${key}: ${e}`); + } + } + + return params; +} + /** * Parses a specific URI string into an object with the well-known properties of * the {@link Location} and/or {@link URL} interfaces implemented by Web @@ -147,7 +169,9 @@ export function getLocationContextRoot(location: Object) { export function parseStandardURIString(str: string) { /* eslint-disable no-param-reassign */ - const obj = {}; + const obj = { + toString: _standardURIToString + }; let regex; let match; @@ -200,9 +224,7 @@ export function parseStandardURIString(str: string) { str = str.substring(regex.lastIndex); } if (pathname) { - if (!pathname.startsWith('/')) { - pathname = `/${pathname}`; - } + pathname.startsWith('/') || (pathname = `/${pathname}`); } else { pathname = '/'; } @@ -263,6 +285,32 @@ export function parseURIString(uri: ?string) { return obj; } +/** + * Implements {@code href} and {@code toString} for the {@code Object} returned + * by {@link #parseStandardURIString}. + * + * @param {Object} [thiz] - An {@code Object} returned by + * {@code #parseStandardURIString} if any; otherwise, it is presumed that the + * function is invoked on such an instance. + * @returns {string} + */ +function _standardURIToString(thiz: ?Object) { + // eslint-disable-next-line no-invalid-this + const { hash, host, pathname, protocol, search } = thiz || this; + let str = ''; + + protocol && (str += protocol); + + // TODO userinfo + + host && (str += `//${host}`); + str += pathname || '/'; + search && (str += search); + hash && (str += hash); + + return str; +} + /** * Attempts to return a {@code String} representation of a specific * {@code Object} which is supposed to represent a URL. Obviously, if a @@ -285,7 +333,7 @@ export function toURLString(obj: ?(string | Object)): ?string { if (obj instanceof URL) { str = obj.href; } else { - str = _urlObjectToString(obj); + str = urlObjectToString(obj); } } break; @@ -303,12 +351,103 @@ export function toURLString(obj: ?(string | Object)): ?string { * {@code Object} similar to the one accepted by the constructor * of Web's ExternalAPI. * - * @param {Object} obj - The URL to return a {@code String} representation of. + * @param {Object} o - The URL to return a {@code String} representation of. * @returns {string} - A {@code String} representation of the specified - * {@code obj}. + * {@code Object}. */ -function _urlObjectToString({ url }: Object): ?string { - // TODO Support properties other than url. Support (pretty much) all - // properties accepted by the constructor of Web's ExternalAPI. - return url; +export function urlObjectToString(o: Object): ?string { + const url = parseStandardURIString(o.url || ''); + + // protocol + if (!url.protocol) { + let protocol = o.protocol || o.scheme; + + if (protocol) { + // Protocol is supposed to be the scheme and the final ':'. Anyway, + // do not make a fuss if the final ':' is not there. + protocol.endsWith(':') || (protocol += ':'); + url.protocol = protocol; + } + } + + // authority & pathname + let { pathname } = url; + + if (!url.host) { + // Web's ExternalAPI domain + // + // It may be host/hostname and pathname with the latter denoting the + // tenant. + const { host, hostname, pathname: contextRoot, port } + = parseStandardURIString(o.domain || o.host || o.hostname); + + // authority + if (host) { + url.host = host; + url.hostname = hostname; + url.port = port; + } + + // pathname + pathname === '/' && contextRoot !== '/' && (pathname = contextRoot); + } + + // pathname + + // Web's ExternalAPI roomName + const room = o.roomName || o.room; + + if (room + && (url.pathname.endsWith('/') + || !url.pathname.endsWith(`/${room}`))) { + pathname.endsWith('/') || (pathname += '/'); + pathname += room; + } + + url.pathname = pathname; + + // query/search + + // Web's ExternalAPI jwt + const { jwt } = o; + + if (jwt) { + let { search } = url; + + if (search.indexOf('?jwt=') === -1 && search.indexOf('&jwt=') === -1) { + search.startsWith('?') || (search = `?${search}`); + search.length === 1 || (search += '&'); + search += `jwt=${jwt}`; + + url.search = search; + } + } + + // fragment/hash + + let { hash } = url; + + for (const configName of [ 'config', 'interfaceConfig' ]) { + const urlParamsArray + = _objectToURLParamsArray( + o[`${configName}Overwrite`] + || o[configName] + || o[`${configName}Override`]); + + if (urlParamsArray.length) { + let urlParamsString + = `${configName}.${urlParamsArray.join(`&${configName}.`)}`; + + if (hash.length) { + urlParamsString = `&${urlParamsString}`; + } else { + hash = '#'; + } + hash += urlParamsString; + } + } + + url.hash = hash; + + return url.toString() || undefined; }