feat(vpaas): Add endpoint counter & remove branding on vpaas meetings

This commit is contained in:
Vlad Piersec 2020-08-25 14:57:35 +03:00 committed by Дамян Минков
parent b7529863d5
commit 71d0577a49
11 changed files with 266 additions and 12 deletions

View File

@ -24,6 +24,7 @@ import {
parseURIString, parseURIString,
toURLString toURLString
} from '../base/util'; } from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions';
import { clearNotifications, showNotification } from '../notifications'; import { clearNotifications, showNotification } from '../notifications';
import { setFatalError } from '../overlay'; 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 close page is enabled redirect to it, without further action
if (enableClosePage) { if (enableClosePage) {
if (isVpaasMeeting(getState())) {
redirectToStaticPage('/');
}
const { isGuest, jwt } = getState()['features/base/jwt']; const { isGuest, jwt } = getState()['features/base/jwt'];
let hashParam; let hashParam;
// save whether current user is guest or not, and pass auth token, // 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/testing/middleware';
import '../base/tracks/middleware'; import '../base/tracks/middleware';
import '../base/user-interaction/middleware'; import '../base/user-interaction/middleware';
import '../billing-counter/middleware';
import '../calendar-sync/middleware'; import '../calendar-sync/middleware';
import '../chat/middleware'; import '../chat/middleware';
import '../conference/middleware'; import '../conference/middleware';

View File

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

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

View File

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