Compare commits

...

9 Commits

Author SHA1 Message Date
Hristo Terezov
d68cddd064 fix(close3): Add close3.js 2020-09-03 14:51:29 -05:00
Tudor-Ovidiu Avram
4ccb0316d8 feat(vpaas) disable deeplinking page 2020-09-03 14:41:30 -05:00
vp8x8
90f5ad3bba fix(vpaas): Fix billing counter auth (#7595) 2020-09-03 14:40:32 -05:00
Vlad Piersec
e12e6e415d fix(vpaas): Fix tenant typo 2020-09-03 14:40:24 -05:00
Vlad Piersec
de22585899 feat(vpaas): Add endpoint counter & remove branding on vpaas meetings 2020-09-03 14:40:15 -05:00
damencho
585ac86d13 feat: Whitelist option to hide lobby button. 2020-09-02 11:50:45 -05:00
Дамян Минков
f69bfe5d61 feat: Adds interface config to hide lobby button. (#7619)
* feat: Adds interface config to hide lobby button.

* squash: Moves the config to config.js and add it to mobile.
2020-09-02 11:13:10 -05:00
Hristo Terezov
ba16f1ed96 fix(iframe-api): setDevice. 2020-08-26 13:58:11 -05:00
Hristo Terezov
4b27b03d51 fix(settings): store url display name and email. 2020-08-26 13:58:01 -05:00
21 changed files with 300 additions and 23 deletions

View File

@@ -51,6 +51,8 @@ deploy-appbundle:
$(BUILD_DIR)/video-blur-effect.min.map \
$(BUILD_DIR)/rnnoise-processor.min.js \
$(BUILD_DIR)/rnnoise-processor.min.map \
$(BUILD_DIR)/close3.min.js \
$(BUILD_DIR)/close3.min.map \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:

View File

@@ -324,6 +324,9 @@ var config = {
// UI
//
// Hides lobby button
// hideLobbyButton: false,
// Require users to always specify a display name.
// requireDisplayName: true,

View File

@@ -24,6 +24,7 @@ import {
parseURIString,
toURLString
} from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions';
import { clearNotifications, showNotification } from '../notifications';
import { setFatalError } from '../overlay';
@@ -291,7 +292,12 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
// if close page is enabled redirect to it, without further action
if (enableClosePage) {
if (isVpaasMeeting(getState())) {
redirectToStaticPage('/');
}
const { isGuest, jwt } = getState()['features/base/jwt'];
let hashParam;
// save whether current user is guest or not, and pass auth token,

View File

@@ -18,6 +18,7 @@ import '../base/sounds/middleware';
import '../base/testing/middleware';
import '../base/tracks/middleware';
import '../base/user-interaction/middleware';
import '../billing-counter/middleware';
import '../calendar-sync/middleware';
import '../chat/middleware';
import '../conference/middleware';

View File

@@ -116,6 +116,7 @@ export default [
'gatherStats',
'googleApiApplicationClientID',
'hiddenDomain',
'hideLobbyButton',
'hosts',
'iAmRecorder',
'iAmSipGateway',

View File

@@ -5,7 +5,6 @@ import { processExternalDeviceRequest } from '../../device-selection';
import { showNotification, showWarningNotification } from '../../notifications';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { CONFERENCE_JOINED } from '../conference';
import { JitsiTrackErrors } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { updateSettings } from '../settings';
@@ -24,6 +23,7 @@ import {
setVideoInputDevice
} from './actions';
import {
areDeviceLabelsInitialized,
formatDeviceLabel,
groupDevicesByKind,
setAudioOutputDeviceId
@@ -73,8 +73,6 @@ function logDeviceList(deviceList) {
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
case NOTIFY_CAMERA_ERROR: {
if (typeof APP !== 'object' || !action.error) {
break;
@@ -148,6 +146,9 @@ MiddlewareRegistry.register(store => next => action => {
break;
case UPDATE_DEVICE_LIST:
logDeviceList(groupDevicesByKind(action.devices));
if (areDeviceLabelsInitialized(store.getState())) {
return _processPendingRequests(store, next, action);
}
break;
case CHECK_AND_NOTIFY_FOR_NEW_DEVICE:
_checkAndNotifyForNewDevice(store, action.newDevices, action.oldDevices);
@@ -170,11 +171,15 @@ MiddlewareRegistry.register(store => next => action => {
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _conferenceJoined({ dispatch, getState }, next, action) {
function _processPendingRequests({ dispatch, getState }, next, action) {
const result = next(action);
const state = getState();
const { pendingRequests } = state['features/base/devices'];
if (!pendingRequests || pendingRequests.length === 0) {
return result;
}
pendingRequests.forEach(request => {
processExternalDeviceRequest(
dispatch,

View File

@@ -2,9 +2,11 @@
import React, { Component } from 'react';
import { isVpaasMeeting } from '../../../../billing-counter/functions';
import { translate } from '../../../i18n';
import { connect } from '../../../redux';
declare var interfaceConfig: Object;
/**
@@ -36,6 +38,11 @@ type Props = {
*/
_isGuest: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
_isVpaas: boolean,
/**
* Flag used to signal that the logo can be displayed.
* It becomes true after the user customization options are fetched.
@@ -181,6 +188,33 @@ class Watermarks extends Component<Props, State> {
|| _welcomePageIsVisible;
}
/**
* Returns the background image style.
*
* @private
* @returns {string}
*/
_getBackgroundImageStyle() {
const {
_customLogoUrl,
_isVpaas,
defaultJitsiLogoURL
} = this.props;
let style = 'none';
if (_isVpaas) {
if (_customLogoUrl) {
style = `url(${_customLogoUrl})`;
}
} else {
style = `url(${_customLogoUrl
|| defaultJitsiLogoURL
|| interfaceConfig.DEFAULT_LOGO_URL})`;
}
return style;
}
/**
* Renders a brand watermark if it is enabled.
*
@@ -221,18 +255,22 @@ class Watermarks extends Component<Props, State> {
*/
_renderJitsiWatermark() {
let reactElement = null;
const {
_customLogoUrl,
_customLogoLink,
defaultJitsiLogoURL
} = this.props;
if (this._canDisplayJitsiWatermark()) {
const link = _customLogoLink || this.state.jitsiWatermarkLink;
const backgroundImage = this._getBackgroundImageStyle();
const link = this.props._customLogoLink || this.state.jitsiWatermarkLink;
const additionalStyles = {};
if (backgroundImage === 'none') {
additionalStyles.height = 0;
additionalStyles.width = 0;
}
const style = {
backgroundImage: `url(${_customLogoUrl || defaultJitsiLogoURL || interfaceConfig.DEFAULT_LOGO_URL})`,
backgroundImage,
maxWidth: 140,
maxHeight: 70
maxHeight: 70,
...additionalStyles
};
reactElement = (<div
@@ -299,6 +337,7 @@ function _mapStateToProps(state) {
_customLogoLink: logoClickUrl,
_customLogoUrl: logoImageUrl,
_isGuest: isGuest,
_isVpaas: isVpaasMeeting(state),
_readyToDisplayJitsiWatermark: customizationReady,
_welcomePageIsVisible: !room
};

View File

@@ -9,6 +9,7 @@ import { MiddlewareRegistry } from '../redux';
import { parseURLParams } from '../util';
import { SETTINGS_UPDATED } from './actionTypes';
import { updateSettings } from './actions';
import { handleCallIntegrationChange, handleCrashReportingChange } from './functions';
/**
@@ -160,10 +161,18 @@ function _updateLocalParticipantFromUrl({ dispatch, getState }) {
const localParticipant = getLocalParticipant(getState());
if (localParticipant) {
const displayName = _.escape(urlDisplayName);
const email = _.escape(urlEmail);
dispatch(participantUpdated({
...localParticipant,
email: _.escape(urlEmail),
name: _.escape(urlDisplayName)
email,
name: displayName
}));
dispatch(updateSettings({
displayName,
email
}));
}
}

View File

@@ -0,0 +1,4 @@
/**
* Action used to store the billing id.
*/
export const SET_BILLING_ID = 'SET_BILLING_ID';

View File

@@ -0,0 +1,51 @@
// @flow
import uuid from 'uuid';
import { SET_BILLING_ID } from './actionTypes';
import { extractVpaasTenantFromPath, getBillingId, sendCountRequest } from './functions';
/**
* Sends a billing count request when needed.
* If there is no billingId, it presists one first and sends the request after.
*
* @returns {Function}
*/
export function countEndpoint() {
return function(dispatch: Function, getState: Function) {
const state = getState();
const baseUrl = state['features/base/config'].billingCounterUrl;
const jwt = state['features/base/jwt'].jwt;
const tenant = extractVpaasTenantFromPath(state['features/base/connection'].locationURL.pathname);
const shouldSendRequest = Boolean(baseUrl && jwt && tenant);
if (shouldSendRequest) {
let billingId = getBillingId();
if (!billingId) {
billingId = uuid.v4();
dispatch(setBillingId(billingId));
}
sendCountRequest({
baseUrl,
billingId,
jwt,
tenant
});
}
};
}
/**
* Action used to set the user billing id.
*
* @param {string} value - The uid.
* @returns {Object}
*/
function setBillingId(value) {
return {
type: SET_BILLING_ID,
value
};
}

View File

@@ -0,0 +1,9 @@
/**
* The key for the billing id stored in localStorage.
*/
export const BILLING_ID = 'billingId';
/**
* The prefix for the vpaas tenant.
*/
export const VPAAS_TENANT_PREFIX = 'vpaas-magic-cookie';

View File

@@ -0,0 +1,91 @@
// @flow
import { jitsiLocalStorage } from '@jitsi/js-utils';
import { BILLING_ID, VPAAS_TENANT_PREFIX } from './constants';
import logger from './logger';
/**
* Returns the full vpaas tenant if available, given a path.
*
* @param {string} path - The meeting url path.
* @returns {string}
*/
export function extractVpaasTenantFromPath(path: string) {
const [ , tenant ] = path.split('/');
if (tenant.startsWith(VPAAS_TENANT_PREFIX)) {
return tenant;
}
return '';
}
/**
* Returns true if the current meeting is a vpaas one.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isVpaasMeeting(state: Object) {
return Boolean(
state['features/base/config'].billingCounterUrl
&& state['features/base/jwt'].jwt
&& extractVpaasTenantFromPath(
state['features/base/connection'].locationURL.pathname)
);
}
/**
* Sends a billing counter request.
*
* @param {Object} reqData - The request info.
* @param {string} reqData.baseUrl - The base url for the request.
* @param {string} billingId - The unique id of the client.
* @param {string} jwt - The JWT token.
* @param {string} tenat - The client tenant.
* @returns {void}
*/
export async function sendCountRequest({ baseUrl, billingId, jwt, tenant }: {
baseUrl: string,
billingId: string,
jwt: string,
tenant: string
}) {
const fullUrl = `${baseUrl}/${encodeURIComponent(tenant)}/${billingId}`;
const headers = {
'Authorization': `Bearer ${jwt}`
};
try {
const res = await fetch(fullUrl, {
method: 'GET',
headers
});
if (!res.ok) {
logger.error('Status error:', res.status);
}
} catch (err) {
logger.error('Could not send request', err);
}
}
/**
* Returns the stored billing id.
*
* @returns {string}
*/
export function getBillingId() {
return jitsiLocalStorage.getItem(BILLING_ID);
}
/**
* Stores the billing id.
*
* @param {string} value - The id to be stored.
* @returns {void}
*/
export function setBillingId(value: string) {
jitsiLocalStorage.setItem(BILLING_ID, value);
}

View File

@@ -0,0 +1,5 @@
// @flow
import { getLogger } from '../base/logging/functions';
export default getLogger('features/billing-counter');

View File

@@ -0,0 +1,31 @@
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { MiddlewareRegistry } from '../base/redux';
import { SET_BILLING_ID } from './actionTypes';
import { countEndpoint } from './actions';
import { setBillingId } from './functions';
/**
* The redux middleware for billing counter.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => async action => {
switch (action.type) {
case SET_BILLING_ID: {
setBillingId(action.value);
break;
}
case CONFERENCE_JOINED: {
store.dispatch(countEndpoint());
break;
}
}
return next(action);
});

View File

@@ -16,6 +16,7 @@ import { translate } from '../../base/i18n';
import { Icon, IconClose } from '../../base/icons';
import { browser } from '../../base/lib-jitsi-meet';
import { connect } from '../../base/redux';
import { isVpaasMeeting } from '../../billing-counter/functions';
import logger from '../logger';
@@ -50,6 +51,11 @@ type Props = {
*/
iAmRecorder: boolean,
/**
* Whether it's a vpaas meeting or not.
*/
isVpaas: boolean,
/**
* Invoked to obtain translated strings.
*/
@@ -146,7 +152,8 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
_isSupportedEnvironment() {
return interfaceConfig.SHOW_CHROME_EXTENSION_BANNER
&& browser.isChrome()
&& !isMobileBrowser();
&& !isMobileBrowser()
&& !this.props.isVpaas;
}
_onClosePressed: () => void;
@@ -280,7 +287,8 @@ const _mapStateToProps = state => {
// Using emptyObject so that we don't change the reference every time when _mapStateToProps is called.
bannerCfg: state['features/base/config'].chromeExtensionBanner || emptyObject,
conference: getCurrentConference(state),
iAmRecorder: state['features/base/config'].iAmRecorder
iAmRecorder: state['features/base/config'].iAmRecorder,
isVpaas: isVpaasMeeting(state)
};
};

View File

@@ -226,7 +226,7 @@ class ConnectionStatsTable extends Component<Props> {
/**
* Creates a a table row as a ReactElement for displaying codec, if present.
* This will typically be something like "Codecs (A/V): opus, vp8".
* This will typically be something like "Codecs (A/V): Opus, vp8".
*
* @private
* @returns {ReactElement}

View File

@@ -3,6 +3,7 @@
import { isMobileBrowser } from '../base/environment/utils';
import { Platform } from '../base/react';
import { URI_PROTOCOL_PATTERN } from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions';
import {
DeepLinkingDesktopPage,
@@ -53,7 +54,7 @@ export function getDeepLinkingPage(state) {
const { launchInWeb } = state['features/deep-linking'];
// Show only if we are about to join a conference.
if (launchInWeb || !room || state['features/base/config'].disableDeepLinking) {
if (launchInWeb || !room || state['features/base/config'].disableDeepLinking || isVpaasMeeting(state)) {
return Promise.resolve();
}

View File

@@ -86,7 +86,6 @@ export function processExternalDeviceRequest( // eslint-disable-line max-params
}
const state = getState();
const settings = state['features/base/settings'];
const { conference } = state['features/base/conference'];
let result = true;
switch (request.name) {
@@ -165,7 +164,7 @@ export function processExternalDeviceRequest( // eslint-disable-line max-params
case 'setDevice': {
const { device } = request;
if (!conference) {
if (!areDeviceLabelsInitialized(state)) {
dispatch(addPendingDeviceRequest({
type: 'devices',
name: 'setDevice',

View File

@@ -66,11 +66,12 @@ class LobbyModeButton extends AbstractButton<Props, any> {
export function _mapStateToProps(state: Object): $Shape<Props> {
const conference = getCurrentConference(state);
const { lobbyEnabled } = state['features/lobby'];
const { hideLobbyButton } = state['features/base/config'];
const lobbySupported = conference && conference.isLobbySupported();
return {
lobbyEnabled,
visible: lobbySupported && isLocalParticipantModerator(state)
visible: lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton
};
}

View File

@@ -132,10 +132,12 @@ class LobbySection extends PureComponent<Props, State> {
*/
function mapStateToProps(state: Object): $Shape<Props> {
const { conference } = state['features/base/conference'];
const { hideLobbyButton } = state['features/base/config'];
return {
_lobbyEnabled: state['features/lobby'].lobbyEnabled,
_visible: conference && conference.isLobbySupported() && isLocalParticipantModerator(state)
&& !hideLobbyButton
};
}

View File

@@ -21,6 +21,7 @@ import {
} from '../../../base/react';
import { connect } from '../../../base/redux';
import { ColorPalette, StyleType } from '../../../base/styles';
import { isVpaasMeeting } from '../../../billing-counter/functions';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
import { RECORDING_TYPES } from '../../constants';
import { getRecordingDurationEstimation } from '../../functions';
@@ -72,6 +73,11 @@ type Props = {
*/
isValidating: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
isVpaas: boolean,
/**
* The function will be called when there are changes related to the
* switches.
@@ -226,7 +232,7 @@ class StartRecordingDialogContent extends Component<Props> {
return null;
}
const { _dialogStyles, _styles: styles, isValidating, t } = this.props;
const { _dialogStyles, _styles: styles, isValidating, isVpaas, t } = this.props;
const switchContent
= this.props.integrationsEnabled
@@ -240,6 +246,8 @@ class StartRecordingDialogContent extends Component<Props> {
value = { this.props.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } />
) : null;
const icon = isVpaas ? ICON_SHARE : JITSI_LOGO;
return (
<Container
className = 'recording-header'
@@ -248,7 +256,7 @@ class StartRecordingDialogContent extends Component<Props> {
<Container className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { JITSI_LOGO }
src = { icon }
style = { styles.recordingIcon } />
</Container>
<Text
@@ -484,6 +492,7 @@ class StartRecordingDialogContent extends Component<Props> {
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent')
};
}