fix: When adding a room param to urls check for previous params. (#11607)

* fix: When adding a room param to urls check for previous params.

* squash: Uses URL object to modify the url.

* squash: Use common connection options from base/connection.

Normalizes bosh url and for web.

* squash: Adds release param to external api and handles it.

* feat: Adds release handling for mobile(links in welcome page).

* squash: Fixes comments.
This commit is contained in:
Дамян Минков 2022-06-16 15:27:41 +03:00 committed by GitHub
parent 7dd85bb6ad
commit f3c6b54ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 137 additions and 95 deletions

View File

@ -8,7 +8,8 @@ import { LoginDialog } from './react/features/authentication/components';
import { isTokenAuthEnabled } from './react/features/authentication/functions'; import { isTokenAuthEnabled } from './react/features/authentication/functions';
import { import {
connectionEstablished, connectionEstablished,
connectionFailed connectionFailed,
constructOptions
} from './react/features/base/connection/actions'; } from './react/features/base/connection/actions';
import { openDialog } from './react/features/base/dialog/actions'; import { openDialog } from './react/features/base/dialog/actions';
import { setJWT } from './react/features/base/jwt'; import { setJWT } from './react/features/base/jwt';
@ -81,12 +82,10 @@ function checkForAttachParametersAndConnect(id, password, connection) {
* Try to open connection using provided credentials. * Try to open connection using provided credentials.
* @param {string} [id] * @param {string} [id]
* @param {string} [password] * @param {string} [password]
* @param {string} [roomName]
* @returns {Promise<JitsiConnection>} connection if * @returns {Promise<JitsiConnection>} connection if
* everything is ok, else error. * everything is ok, else error.
*/ */
export async function connect(id, password, roomName) { export async function connect(id, password) {
const connectionConfig = Object.assign({}, config);
const state = APP.store.getState(); const state = APP.store.getState();
let { jwt } = state['features/base/jwt']; let { jwt } = state['features/base/jwt'];
const { iAmRecorder, iAmSipGateway } = state['features/base/config']; const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
@ -100,19 +99,7 @@ export async function connect(id, password, roomName) {
} }
} }
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
serviceUrl += `?room=${roomName}`;
connectionConfig.serviceUrl = serviceUrl;
if (connectionConfig.websocketKeepAliveUrl) {
connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
if (config.iAmRecorder) { if (config.iAmRecorder) {
connection.addFeature(DISCO_JIBRI_FEATURE); connection.addFeature(DISCO_JIBRI_FEATURE);

View File

@ -301,6 +301,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* the participant opening the meeting. * the participant opening the meeting.
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption. * @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
* THIS IS EXPERIMENTAL. * THIS IS EXPERIMENTAL.
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
*/ */
constructor(domain, ...args) { constructor(domain, ...args) {
super(); super();
@ -317,7 +318,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
invitees, invitees,
devices, devices,
userInfo, userInfo,
e2eeKey e2eeKey,
release
} = parseArguments(args); } = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage'); const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
@ -332,7 +334,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
userInfo, userInfo,
appData: { appData: {
localStorageContent localStorageContent
} },
release
}); });
this._createIFrame(height, width, onload); this._createIFrame(height, width, onload);
this._transport = new Transport({ this._transport = new Transport({

View File

@ -15,8 +15,10 @@ import { connect, disconnect, setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native'; import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks'; import { createDesiredLocalTracks } from '../base/tracks';
import { import {
appendURLParam,
getBackendSafeRoomName, getBackendSafeRoomName,
parseURIString, parseURIString,
parseURLParams,
toURLString toURLString
} from '../base/util'; } from '../base/util';
import { isPrejoinPageEnabled } from '../mobile/navigation/functions'; import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
@ -90,7 +92,11 @@ export function appNavigate(uri: ?string) {
let url = `${baseURL}config.js`; let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment. // XXX In order to support multiple shards, tell the room to the deployment.
room && (url += `?room=${getBackendSafeRoomName(room)}`); room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
const { release } = parseURLParams(location, true, 'search');
release && (url = appendURLParam(url, 'release', release));
let config; let config;

View File

@ -15,8 +15,10 @@ import {
import { setLocationURL } from '../base/connection'; import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet/functions.web'; import { loadConfig } from '../base/lib-jitsi-meet/functions.web';
import { import {
appendURLParam,
getBackendSafeRoomName, getBackendSafeRoomName,
parseURIString parseURIString,
parseURLParams
} from '../base/util'; } from '../base/util';
import { isVpaasMeeting } from '../jaas/functions'; import { isVpaasMeeting } from '../jaas/functions';
import { import {
@ -93,7 +95,11 @@ export function appNavigate(uri: ?string) {
let url = `${baseURL}config.js`; let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment. // XXX In order to support multiple shards, tell the room to the deployment.
room && (url += `?room=${getBackendSafeRoomName(room)}`); room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
const { release } = parseURLParams(location, true, 'search');
release && (url = appendURLParam(url, 'release', release));
let config; let config;

View File

@ -0,0 +1,71 @@
import _ from 'lodash';
import {
appendURLParam,
getBackendSafeRoomName,
parseURIString
} from '../util';
import logger from './logger';
/**
* Constructs options to be passed to the constructor of {@code JitsiConnection}
* based on the redux state.
*
* @param {Object} state - The redux state.
* @returns {Object} The options to be passed to the constructor of
* {@code JitsiConnection}.
*/
export function constructOptions(state) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options = _.cloneDeep(state['features/base/config']);
let { bosh, websocket } = options;
// TESTING: Only enable WebSocket for some percentage of users.
if (websocket && navigator.product === 'ReactNative') {
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
websocket = undefined;
}
}
// Normalize the BOSH URL.
if (bosh && !websocket) {
const { locationURL } = state['features/base/connection'];
if (bosh.startsWith('//')) {
// By default our config.js doesn't include the protocol.
bosh = `${locationURL.protocol}${bosh}`;
} else if (bosh.startsWith('/')) {
// Handle relative URLs, which won't work on mobile.
const {
protocol,
host,
contextRoot
} = parseURIString(locationURL.href);
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
}
}
// WebSocket is preferred over BOSH.
const serviceUrl = websocket || bosh;
logger.log(`Using service URL ${serviceUrl}`);
// Append room to the URL's search.
const { room } = state['features/base/conference'];
if (serviceUrl && room) {
const roomName = getBackendSafeRoomName(room);
options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName);
if (options.websocketKeepAliveUrl) {
options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName);
}
}
return options;
}

View File

@ -1,15 +1,10 @@
// @flow // @flow
import _ from 'lodash';
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { conferenceLeft, conferenceWillLeave } from '../conference/actions'; import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
import { getCurrentConference } from '../conference/functions'; import { getCurrentConference } from '../conference/functions';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet'; import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import {
getBackendSafeRoomName,
parseURIString
} from '../util';
import { import {
CONNECTION_DISCONNECTED, CONNECTION_DISCONNECTED,
@ -18,9 +13,12 @@ import {
CONNECTION_WILL_CONNECT, CONNECTION_WILL_CONNECT,
SET_LOCATION_URL SET_LOCATION_URL
} from './actionTypes'; } from './actionTypes';
import { constructOptions } from './actions.any';
import { JITSI_CONNECTION_URL_KEY } from './constants'; import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger'; import logger from './logger';
export * from './actions.any';
/** /**
* The error structure passed to the {@link connectionFailed} action. * The error structure passed to the {@link connectionFailed} action.
* *
@ -78,7 +76,7 @@ export type ConnectionFailedError = {
export function connect(id: ?string, password: ?string) { export function connect(id: ?string, password: ?string) {
return (dispatch: Dispatch<any>, getState: Function) => { return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState(); const state = getState();
const options = _constructOptions(state); const options = constructOptions(state);
const { locationURL } = state['features/base/connection']; const { locationURL } = state['features/base/connection'];
const { jwt } = state['features/base/jwt']; const { jwt } = state['features/base/jwt'];
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options); const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
@ -262,69 +260,6 @@ function _connectionWillConnect(connection) {
}; };
} }
/**
* Constructs options to be passed to the constructor of {@code JitsiConnection}
* based on the redux state.
*
* @param {Object} state - The redux state.
* @returns {Object} The options to be passed to the constructor of
* {@code JitsiConnection}.
*/
function _constructOptions(state) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options = _.cloneDeep(state['features/base/config']);
let { bosh, websocket } = options;
// TESTING: Only enable WebSocket for some percentage of users.
if (websocket) {
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
websocket = undefined;
}
}
// Normalize the BOSH URL.
if (bosh && !websocket) {
const { locationURL } = state['features/base/connection'];
if (bosh.startsWith('//')) {
// By default our config.js doesn't include the protocol.
bosh = `${locationURL.protocol}${bosh}`;
} else if (bosh.startsWith('/')) {
// Handle relative URLs, which won't work on mobile.
const {
protocol,
host,
contextRoot
} = parseURIString(locationURL.href);
// eslint-disable-next-line max-len
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
}
}
// WebSocket is preferred over BOSH.
const serviceUrl = websocket || bosh;
logger.log(`Using service URL ${serviceUrl}`);
// Append room to the URL's search.
const { room } = state['features/base/conference'];
if (serviceUrl && room) {
const roomName = getBackendSafeRoomName(room);
options.serviceUrl = `${serviceUrl}?room=${roomName}`;
if (options.websocketKeepAliveUrl) {
options.websocketKeepAliveUrl += `?room=${roomName}`;
}
}
return options;
}
/** /**
* Closes connection. * Closes connection.
* *

View File

@ -16,6 +16,8 @@ export {
} from './actions.native'; } from './actions.native';
import logger from './logger'; import logger from './logger';
export * from './actions.any';
/** /**
* Opens new connection. * Opens new connection.
* *

View File

@ -524,7 +524,7 @@ export function urlObjectToString(o: Object): ?string {
// query/search // query/search
// Web's ExternalAPI jwt and lang // Web's ExternalAPI jwt and lang
const { jwt, lang } = o; const { jwt, lang, release } = o;
const search = new URLSearchParams(url.search); const search = new URLSearchParams(url.search);
@ -538,6 +538,10 @@ export function urlObjectToString(o: Object): ?string {
search.set('lang', lang || defaultLanguage); search.set('lang', lang || defaultLanguage);
} }
if (release) {
search.set('release', release);
}
const searchString = search.toString(); const searchString = search.toString();
if (searchString) { if (searchString) {
@ -603,3 +607,20 @@ export function addHashParamsToURL(url: URL, hashParamsToAdd: Object = {}) {
export function getDecodedURI(uri: string) { export function getDecodedURI(uri: string) {
return decodeURI(uri.replace(/^https?:\/\//i, '')); return decodeURI(uri.replace(/^https?:\/\//i, ''));
} }
/**
* Adds new param to a url string. Checks whether to use '?' or '&' as a separator (checks for already existing params).
*
* @param {string} url - The url to modify.
* @param {string} name - The param name to add.
* @param {string} value - The value for the param.
*
* @returns {string} - The modified url.
*/
export function appendURLParam(url: string, name: string, value: string) {
const newUrl = new URL(url);
newUrl.searchParams.append(name, value);
return newUrl.toString();
}

View File

@ -8,7 +8,11 @@ import { i18next } from '../base/i18n';
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet'; import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants'; import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { parseURIString } from '../base/util'; import {
appendURLParam,
parseURIString,
parseURLParams
} from '../base/util';
import { isVpaasMeeting } from '../jaas/functions'; import { isVpaasMeeting } from '../jaas/functions';
import { getDialInConferenceID, getDialInNumbers } from './_utils'; import { getDialInConferenceID, getDialInNumbers } from './_utils';
@ -596,7 +600,7 @@ export function getDialInfoPageURL(state: Object, roomName: ?string) {
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/${DIAL_IN_INFO_PAGE_PATH_NAME}`; const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/${DIAL_IN_INFO_PAGE_PATH_NAME}`;
return `${url}?room=${room}`; return appendURLParam(url, 'room', room);
} }
/** /**
@ -611,8 +615,15 @@ export function getDialInfoPageURLForURIString(
return undefined; return undefined;
} }
const { protocol, host, contextRoot, room } = parseURIString(uri); const { protocol, host, contextRoot, room } = parseURIString(uri);
let url = `${protocol}//${host}${contextRoot}${DIAL_IN_INFO_PAGE_PATH_NAME}`;
return `${protocol}//${host}${contextRoot}${DIAL_IN_INFO_PAGE_PATH_NAME}?room=${room}`; url = appendURLParam(url, 'room', room);
const { release } = parseURLParams(uri, true, 'search');
release && (url = appendURLParam(url, 'release', release));
return url;
} }
/** /**