Compare commits

...

14 Commits

Author SHA1 Message Date
damencho
4f0f8ea019 fix(tests): Skip iframeAPI if it is disabled. 2025-02-26 14:35:11 -06:00
damencho
444f4a7abc feat(tests): Adds an option for an access jwt token.
Used only for the first participant joining/creating the room.
2025-02-25 09:17:58 -06:00
Hristo Terezov
ee6bf011e9 feat(config): Add logger.warn for depricated params.
This includes interfaceConfig.SUPPORT_URL, interfaceConfig.LIVE_STREAMING_HELP_LINK, config.defaultLogoUrl, config.deploymentUrls, config.liveStreaming and config.customToolbarButtons.
2025-02-25 07:41:53 -06:00
Hristo Terezov
bea8a7f984 fix(configWhitelist): Remove customToolbarButtons. 2025-02-25 07:41:53 -06:00
Hristo Terezov
2edca5dacb fix(analytics): overwritesCustomButtonsWithURL metric
Count all customButtons overrides not only the ones that are not including data URLs.
2025-02-25 07:41:53 -06:00
Hristo Terezov
69ac73c556 feat(dynamic-branding): Add customToolbarButtons. 2025-02-25 07:41:53 -06:00
Hristo Terezov
89556ecd66 feat(dynamic-branding): Add customParticipantMenuButtons 2025-02-25 07:41:53 -06:00
Hristo Terezov
462f91f070 feat(dynamic-branding): Add etherpadBase 2025-02-25 07:41:53 -06:00
Hristo Terezov
d29a77b15f feat(dynamic-branding): Add peopleSearchUrl 2025-02-25 07:41:53 -06:00
Hristo Terezov
c31fe521c4 feat(analytics): remove overwritesPrejoinConfigICEUrl 2025-02-25 07:41:53 -06:00
Hristo Terezov
8f6f542e9c feat(inIframe-whitelists): Implement.
Now we are able to have a whitelist for config and interface config that will be used only for the case where jitsi-meet is loaded in an IFrame.
2025-02-25 07:41:53 -06:00
Hristo Terezov
69d9e7d405 ref(analytics): remove overwritesHosts 2025-02-25 07:41:53 -06:00
Hristo Terezov
5e6748a88a ref(analytics): remove overwritesIceServers 2025-02-25 07:41:53 -06:00
Hristo Terezov
8bc70f9c87 fix(iceServers): Restrict iceServers url param to iframe only. 2025-02-25 07:41:53 -06:00
14 changed files with 119 additions and 65 deletions

View File

@@ -120,7 +120,9 @@ UI.unbindEvents = () => {
* @param {string} name etherpad id
*/
UI.initEtherpad = name => {
const etherpadBaseUrl = sanitizeUrl(config.etherpad_base);
const { getState, dispatch } = APP.store;
const configState = getState()['features/base/config'];
const etherpadBaseUrl = sanitizeUrl(configState.etherpad_base);
if (etherpadManager || !etherpadBaseUrl || !name) {
return;
@@ -131,9 +133,9 @@ UI.initEtherpad = name => {
const url = new URL(name, etherpadBaseUrl);
APP.store.dispatch(setDocumentUrl(url.toString()));
dispatch(setDocumentUrl(url.toString()));
if (config.openSharedDocumentOnJoin) {
if (configState.openSharedDocumentOnJoin) {
etherpadManager.toggleEtherpad();
}
};

View File

@@ -152,30 +152,6 @@ export async function createHandlers({ getState }: IStore) {
return handlers;
}
/**
* Checks whether a url is a data URL or not.
*
* @param {string} url - The URL to be checked.
* @returns {boolean}
*/
function isDataURL(url?: string): boolean {
if (typeof url !== 'string') { // The icon will be ignored
return false;
}
try {
const urlObject = new URL(url);
if (urlObject.protocol === 'data:') {
return false;
}
} catch {
return false;
}
return true;
}
/**
* Inits JitsiMeetJS.analytics by setting permanent properties and setting the handlers from the loaded scripts.
* NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be null.
@@ -208,15 +184,9 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
isPromotedFromVisitor?: boolean;
isVisitor?: boolean;
overwritesCustomButtonsWithURL?: boolean;
overwritesCustomParticipantButtonsWithURL?: boolean;
overwritesDefaultLogoUrl?: boolean;
overwritesDeploymentUrls?: boolean;
overwritesEtherpadBase?: boolean;
overwritesHosts?: boolean;
overwritesIceServers?: boolean;
overwritesLiveStreamingUrls?: boolean;
overwritesPeopleSearchUrl?: boolean;
overwritesPrejoinConfigICEUrl?: boolean;
overwritesSalesforceUrl?: boolean;
overwritesSupportUrl?: boolean;
server?: string;
@@ -260,19 +230,8 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
// TODO: Temporary metric. To be removed once we don't need it.
permanentProperties.overwritesSupportUrl = 'interfaceConfig.SUPPORT_URL' in params;
permanentProperties.overwritesSalesforceUrl = 'config.salesforceUrl' in params;
permanentProperties.overwritesPeopleSearchUrl = 'config.peopleSearchUrl' in params;
permanentProperties.overwritesDefaultLogoUrl = 'config.defaultLogoUrl' in params;
permanentProperties.overwritesEtherpadBase = 'config.etherpad_base' in params;
const hosts = params['config.hosts'] ?? {};
const hostsProps = [ 'anonymousdomain', 'authdomain', 'domain', 'focus', 'muc', 'visitorFocus' ];
permanentProperties.overwritesHosts = 'config.hosts' in params
|| Boolean(hostsProps.find(p => `config.hosts.${p}` in params || (typeof hosts === 'object' && p in hosts)));
const prejoinConfig = params['config.prejoinConfig'] ?? {};
permanentProperties.overwritesPrejoinConfigICEUrl = ('config.prejoinConfig.preCallTestICEUrl' in params)
|| (typeof prejoinConfig === 'object' && 'preCallTestICEUrl' in prejoinConfig);
const deploymentUrlsConfig = params['config.deploymentUrls'] ?? {};
permanentProperties.overwritesDeploymentUrls
@@ -294,17 +253,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
)
);
permanentProperties.overwritesIceServers = Boolean(Object.keys(params).find(k => k.startsWith('iceServers')));
const customToolbarButtons = params['config.customToolbarButtons'] ?? [];
permanentProperties.overwritesCustomButtonsWithURL = Boolean(
customToolbarButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
const customParticipantMenuButtons = params['config.customParticipantMenuButtons'] ?? [];
permanentProperties.overwritesCustomParticipantButtonsWithURL = Boolean(
customParticipantMenuButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
permanentProperties.overwritesCustomButtonsWithURL = 'config.customToolbarButtons' in params;
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.

View File

@@ -75,7 +75,7 @@ export const _getTokenAuthState = (
const params = parseURLParams(locationURL);
for (const key of Object.keys(params)) {
// we allow only config and interfaceConfig overrides in the state
// we allow only config, interfaceConfig and iceServers overrides in the state
if (key.startsWith('config.') || key.startsWith('interfaceConfig.') || key.startsWith('iceServers.')) {
// @ts-ignore
state[key] = params[key];

View File

@@ -1,4 +1,7 @@
import { inIframe } from '../util/iframeUtils';
import extraConfigWhitelist from './extraConfigWhitelist';
import inIframeConfigWhitelist from './inIframeConfigWhitelist';
/**
* The config keys to whitelist, the keys that can be overridden.
@@ -77,7 +80,6 @@ export default [
'channelLastN',
'connectionIndicators',
'constraints',
'customToolbarButtons',
'deeplinking.disabled',
'deeplinking.desktop.enabled',
'defaultLocalDisplayName',
@@ -243,4 +245,4 @@ export default [
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',
'whiteboard.enabled'
].concat(extraConfigWhitelist);
].concat(extraConfigWhitelist).concat(inIframe() ? inIframeConfigWhitelist : []);

View File

@@ -327,6 +327,49 @@ export function setConfigFromURLParams(
}
overrideConfigJSON(config, interfaceConfig, json);
// Print warning about depricated URL params
if ('interfaceConfig.SUPPORT_URL' in params) {
logger.warn('Using SUPPORT_URL interfaceConfig URL overwrite is deprecated.'
+ ' Please use supportUrl from advanced branding!');
}
if ('config.defaultLogoUrl' in params) {
logger.warn('Using defaultLogoUrl config URL overwrite is deprecated.'
+ ' Please use logoImageUrl from advanced branding!');
}
const deploymentUrlsConfig = params['config.deploymentUrls'] ?? {};
if ('config.deploymentUrls.downloadAppsUrl' in params || 'config.deploymentUrls.userDocumentationURL' in params
|| (typeof deploymentUrlsConfig === 'object'
&& ('downloadAppsUrl' in deploymentUrlsConfig || 'userDocumentationURL' in deploymentUrlsConfig))) {
logger.warn('Using deploymentUrls config URL overwrite is deprecated.'
+ ' Please use downloadAppsUrl and/or userDocumentationURL from advanced branding!');
}
const liveStreamingConfig = params['config.liveStreaming'] ?? {};
if (('interfaceConfig.LIVE_STREAMING_HELP_LINK' in params)
|| ('config.liveStreaming.termsLink' in params)
|| ('config.liveStreaming.dataPrivacyLink' in params)
|| ('config.liveStreaming.helpLink' in params)
|| (typeof params['config.liveStreaming'] === 'object' && 'config.liveStreaming' in params
&& (
'termsLink' in liveStreamingConfig
|| 'dataPrivacyLink' in liveStreamingConfig
|| 'helpLink' in liveStreamingConfig
)
)) {
logger.warn('Using liveStreaming config URL overwrite and/or LIVE_STREAMING_HELP_LINK interfaceConfig URL'
+ ' overwrite is deprecated. Please use liveStreaming from advanced branding!');
}
if ('config.customToolbarButtons' in params) {
logger.warn('Using customToolbarButtons config URL overwrite is deprecated.'
+ ' Please use liveStreaming from advanced branding!');
}
}
/* eslint-enable max-params */

View File

@@ -0,0 +1,4 @@
/**
* Additional config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an iframe.
*/
export default [];

View File

@@ -0,0 +1,5 @@
/**
* Additional interface config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an
* iframe.
*/
export default [];

View File

@@ -1,4 +1,7 @@
import { inIframe } from '../util/iframeUtils';
import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist';
import inIframeInterfaceConfigWhitelist from './inIframeInterfaceConfigWhitelist';
/**
* The interface config keys to whitelist, the keys that can be overridden.
@@ -51,4 +54,4 @@ export default [
'VERTICAL_FILMSTRIP',
'VIDEO_LAYOUT_FIT',
'VIDEO_QUALITY_LABEL_DISABLED'
].concat(extraInterfaceConfigWhitelistCopy);
].concat(extraInterfaceConfigWhitelistCopy).concat(inIframe() ? inIframeInterfaceConfigWhitelist : []);

View File

@@ -114,11 +114,15 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: AnyAction) {
const config: IConfig = {};
const {
customParticipantMenuButtons,
customToolbarButtons,
downloadAppsUrl,
etherpadBase,
liveStreamingDialogUrls = {},
preCallTest = {},
salesforceUrl,
userDocumentationUrl
userDocumentationUrl,
peopleSearchUrl,
} = action.value;
const { helpUrl, termsUrl, dataPrivacyUrl } = liveStreamingDialogUrls;
@@ -154,6 +158,10 @@ function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: A
config.salesforceUrl = salesforceUrl;
}
if (peopleSearchUrl) {
config.peopleSearchUrl = peopleSearchUrl;
}
const { enabled, iceUrl } = preCallTest;
if (typeof enabled === 'boolean') {
@@ -162,11 +170,24 @@ function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: A
};
}
if (etherpadBase) {
// eslint-disable-next-line camelcase
config.etherpad_base = etherpadBase;
}
if (iceUrl) {
config.prejoinConfig = config.prejoinConfig || {};
config.prejoinConfig.preCallTestICEUrl = iceUrl;
}
if (customToolbarButtons) {
config.customToolbarButtons = customToolbarButtons;
}
if (customParticipantMenuButtons) {
config.customParticipantMenuButtons = customParticipantMenuButtons;
}
dispatch(updateConfig(config));
return next(action);

View File

@@ -5,6 +5,7 @@ import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/act
import { getCurrentConference } from '../conference/functions';
import { IConfigState } from '../config/reducer';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import { inIframe } from '../util/iframeUtils';
import { parseURLParams } from '../util/parseURLParams';
import {
appendURLParam,
@@ -119,7 +120,8 @@ export function constructOptions(state: IReduxState) {
const params = parseURLParams(locationURL || '');
const iceServersOverride = params['iceServers.replace'];
if (iceServersOverride) {
// Allow iceServersOverride only when jitsi-meet is in an iframe.
if (inIframe() && iceServersOverride) {
options.iceServersOverride = iceServersOverride;
}

View File

@@ -22,15 +22,19 @@ MiddlewareRegistry.register(store => next => action => {
backgroundColor,
backgroundImageUrl,
brandedIcons,
customParticipantMenuButtons,
customToolbarButtons,
didPageUrl,
downloadAppsUrl,
etherpadBase,
inviteDomain,
labels,
liveStreamingDialogUrls,
peopleSearchUrl,
salesforceUrl,
sharedVideoAllowedURLDomains,
supportUrl,
userDocumentationUrl
userDocumentationUrl,
} = action.value;
action.value = {
@@ -38,11 +42,15 @@ MiddlewareRegistry.register(store => next => action => {
backgroundColor,
backgroundImageUrl,
brandedIcons,
customParticipantMenuButtons,
customToolbarButtons,
didPageUrl,
downloadAppsUrl,
etherpadBase,
inviteDomain,
labels,
liveStreamingDialogUrls,
peopleSearchUrl,
salesforceUrl,
sharedVideoAllowedURLDomains,
supportUrl,

View File

@@ -26,6 +26,9 @@
# The kid to use in the token
#JWT_KID=
# An access token to use to create meetings (used for the first participant)
#JWT_ACCESS_TOKEN=
# The count of workers that execute the tests in parallel
# MAX_INSTANCES=1

View File

@@ -165,9 +165,14 @@ async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
const p1DisplayName = P1_DISPLAY_NAME;
let token;
// if it is jaas create the first one to be moderator and second not moderator
if (ctx.jwtPrivateKeyPath && !options?.skipFirstModerator) {
token = getModeratorToken(p1DisplayName);
if (!options?.skipFirstModerator) {
// we prioritize the access token when iframe is not used and private key is set,
// otherwise if private key is not specified we use the access token if set
if (process.env.JWT_ACCESS_TOKEN && (!ctx.jwtPrivateKeyPath || !ctx.iframeAPI)) {
token = process.env.JWT_ACCESS_TOKEN;
} else if (ctx.jwtPrivateKeyPath) {
token = getModeratorToken(p1DisplayName);
}
}
// make sure the first participant is moderator, if supported by deployment

View File

@@ -37,6 +37,13 @@ describe('Participants presence', () => {
const { p1, p2, webhooksProxy } = ctx;
if (await p1.execute(() => config.disableIframeAPI)) {
// skip the test if iframeAPI is disabled
ctx.skipSuiteTests = true;
return;
}
// let's populate endpoint ids
await Promise.all([
p1.getEndpointId(),