diff --git a/lang/main.json b/lang/main.json index 0299cf89dc..da3a2813f0 100644 --- a/lang/main.json +++ b/lang/main.json @@ -870,9 +870,11 @@ "lookGood": "Your microphone is working properly", "or": "or", "premeeting": "Pre meeting", + "proceedAnyway": "Proceed anyway", "screenSharingError": "Screen sharing error:", "showScreen": "Enable pre meeting screen", "startWithPhone": "Start with phone audio", + "unsafeRoomConsent": "I understand the risks, I want to join the meeting", "videoOnlyError": "Video error:", "videoTrackError": "Could not create video track.", "viewAllNumbers": "view all numbers" @@ -974,8 +976,14 @@ "security": { "about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.", "aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.", - "insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.", - "title": "Security Options" + "insecureRoomNameWarningNative": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting ", + "insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting here.", + "title": "Security Options", + "unsafeRoomActions": { + "meeting": "Consider securing your meeting using the security button.", + "prejoin": "Consider using a more unique meeting name.", + "welcome": "Consider using a more unique meeting name, or pick one of the suggestions." + } }, "settings": { "audio": "Audio", diff --git a/react/features/app/actions.native.ts b/react/features/app/actions.native.ts index 84740276f1..98c61e1eb3 100644 --- a/react/features/app/actions.native.ts +++ b/react/features/app/actions.native.ts @@ -12,6 +12,7 @@ import { import { connect, disconnect, setLocationURL } from '../base/connection/actions'; import { loadConfig } from '../base/lib-jitsi-meet/functions.native'; import { createDesiredLocalTracks } from '../base/tracks/actions'; +import isInsecureRoomName from '../base/util/isInsecureRoomName'; import { parseURLParams } from '../base/util/parseURLParams'; import { appendURLParam, @@ -136,6 +137,11 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) { dispatch(setRoom(room)); if (room) { + if (isInsecureRoomName(room)) { + navigateRoot(screen.unsafeRoomWarning); + + return; + } dispatch(createDesiredLocalTracks()); dispatch(clearNotifications()); diff --git a/react/features/app/reducers.web.ts b/react/features/app/reducers.web.ts index a8075eb332..071d0534ef 100644 --- a/react/features/app/reducers.web.ts +++ b/react/features/app/reducers.web.ts @@ -1,4 +1,5 @@ import '../base/devices/reducer'; +import '../base/premeeting/reducer'; import '../base/tooltip/reducer'; import '../e2ee/reducer'; import '../face-landmarks/reducer'; diff --git a/react/features/app/types.ts b/react/features/app/types.ts index 485895d5a5..9293206b5c 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -20,6 +20,7 @@ import { ILoggingState } from '../base/logging/reducer'; import { IMediaState } from '../base/media/reducer'; import { INetInfoState } from '../base/net-info/reducer'; import { IParticipantsState } from '../base/participants/reducer'; +import { IPreMeetingState } from '../base/premeeting/types'; import { IResponsiveUIState } from '../base/responsive-ui/reducer'; import { ISettingsState } from '../base/settings/reducer'; import { ISoundsState } from '../base/sounds/reducer'; @@ -110,6 +111,7 @@ export interface IReduxState { 'features/base/net-info': INetInfoState; 'features/base/no-src-data': INoSrcDataState; 'features/base/participants': IParticipantsState; + 'features/base/premeeting': IPreMeetingState; 'features/base/responsive-ui': IResponsiveUIState; 'features/base/settings': ISettingsState; 'features/base/sounds': ISoundsState; diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index 096d989ef9..80ad10c5fa 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -408,6 +408,7 @@ export interface IConfig { legalUrls?: { helpCentre: string; privacy: string; + security: string; terms: string; }; liveStreaming?: { diff --git a/react/features/base/premeeting/actionTypes.ts b/react/features/base/premeeting/actionTypes.ts new file mode 100644 index 0000000000..82400d3564 --- /dev/null +++ b/react/features/base/premeeting/actionTypes.ts @@ -0,0 +1,9 @@ +/** + * Type for setting the user's consent for unsafe room joining. + * + * { + * type: SET_UNSAFE_ROOM_CONSENT, + * consent: boolean + * } + */ +export const SET_UNSAFE_ROOM_CONSENT = 'SET_UNSAFE_ROOM_CONSENT' \ No newline at end of file diff --git a/react/features/base/premeeting/actions.web.ts b/react/features/base/premeeting/actions.web.ts new file mode 100644 index 0000000000..79013f08b4 --- /dev/null +++ b/react/features/base/premeeting/actions.web.ts @@ -0,0 +1,17 @@ +import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes'; + +/** + * Sets the consent of the user for joining the unsafe room. + * + * @param {boolean} consent - The user's consent. + * @returns {{ + * type: SET_UNSAFE_ROOM_CONSENT, +* consent: boolean +* }} + */ +export function setUnsafeRoomConsent(consent: boolean) { + return { + type: SET_UNSAFE_ROOM_CONSENT, + consent + }; +} diff --git a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx index 7a7c5925f6..7df61848b4 100644 --- a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx +++ b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx @@ -13,6 +13,7 @@ import { withPixelLineHeight } from '../../../styles/functions.web'; import ConnectionStatus from './ConnectionStatus'; import Preview from './Preview'; +import UnsafeRoomWarning from './UnsafeRoomWarning'; interface IProps { @@ -56,6 +57,11 @@ interface IProps { */ showDeviceStatus: boolean; + /** + * If should show unsafe room warning when joining. + */ + showUnsafeRoomWarning?: boolean; + /** * The 'Skip prejoin' button to be rendered (if any). */ @@ -161,6 +167,7 @@ const PreMeetingScreen = ({ children, className, showDeviceStatus, + showUnsafeRoomWarning, skipPrejoinButton, title, videoMuted, @@ -191,6 +198,7 @@ const PreMeetingScreen = ({ {children} {_buttons.length && } {skipPrejoinButton} + {showUnsafeRoomWarning && } {showDeviceStatus && } diff --git a/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx b/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx new file mode 100644 index 0000000000..f4e3cc3d42 --- /dev/null +++ b/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx @@ -0,0 +1,54 @@ +import React, { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; +import { makeStyles } from 'tss-react/mui'; + +import { IReduxState } from '../../../../app/types'; +import { withPixelLineHeight } from '../../../styles/functions.web'; +import Checkbox from '../../../ui/components/web/Checkbox'; +import getUnsafeRoomText from '../../../util/getUnsafeRoomText.web'; +import { setUnsafeRoomConsent } from '../../actions.web'; + +const useStyles = makeStyles()(theme => { + return { + warning: { + backgroundColor: theme.palette.warning01, + color: theme.palette.text04, + ...withPixelLineHeight(theme.typography.bodyShortRegular), + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + marginBottom: theme.spacing(3) + }, + consent: { + padding: `0 ${theme.spacing(3)}`, + '@media (max-width: 720px)': { + marginBottom: theme.spacing(3) + } + } + }; +}); + +const UnsafeRoomWarning = () => { + const { t } = useTranslation(); + const { classes } = useStyles(); + const dispatch = useDispatch(); + const { unsafeRoomConsent } = useSelector((state: IReduxState) => state['features/base/premeeting']); + const toggleConsent = useCallback( + () => dispatch(setUnsafeRoomConsent(!unsafeRoomConsent)) + , [ unsafeRoomConsent, dispatch ]); + + return ( + <> +
+ {getUnsafeRoomText(t, 'prejoin')} +
+ + + ); +}; + +export default UnsafeRoomWarning; diff --git a/react/features/base/premeeting/reducer.web.ts b/react/features/base/premeeting/reducer.web.ts new file mode 100644 index 0000000000..37ce364373 --- /dev/null +++ b/react/features/base/premeeting/reducer.web.ts @@ -0,0 +1,33 @@ +import ReducerRegistry from '../redux/ReducerRegistry'; + +import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes'; +import { IPreMeetingState } from './types'; + + +const DEFAULT_STATE: IPreMeetingState = { + unsafeRoomConsent: false +}; + +/** + * Listen for actions which changes the state of known and used devices. + * + * @param {IDevicesState} state - The Redux state of the feature features/base/devices. + * @param {Object} action - Action object. + * @param {string} action.type - Type of action. + * @returns {IPreMeetingState} + */ +ReducerRegistry.register( + 'features/base/premeeting', + (state = DEFAULT_STATE, action): IPreMeetingState => { + switch (action.type) { + case SET_UNSAFE_ROOM_CONSENT: { + return { + ...state, + unsafeRoomConsent: action.consent + }; + } + default: + return state; + } + }); + diff --git a/react/features/base/premeeting/types.ts b/react/features/base/premeeting/types.ts new file mode 100644 index 0000000000..ff431750ec --- /dev/null +++ b/react/features/base/premeeting/types.ts @@ -0,0 +1,3 @@ +export interface IPreMeetingState { + unsafeRoomConsent?: boolean; +} diff --git a/react/features/base/tooltip/actions.tsx b/react/features/base/tooltip/actions.tsx index 3d2aa2bd37..0b9c1c8a5d 100644 --- a/react/features/base/tooltip/actions.tsx +++ b/react/features/base/tooltip/actions.tsx @@ -1,3 +1,5 @@ +import { ReactElement } from 'react'; + import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes'; /** @@ -7,7 +9,7 @@ import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes'; * Used as unique identifier for tooltip. * @returns {Object} */ -export function showTooltip(content: string) { +export function showTooltip(content: string | ReactElement) { return { type: SHOW_TOOLTIP, content @@ -21,7 +23,7 @@ export function showTooltip(content: string) { * Used as unique identifier for tooltip. * @returns {Object} */ -export function hideTooltip(content: string) { +export function hideTooltip(content: string | ReactElement) { return { type: HIDE_TOOLTIP, content diff --git a/react/features/base/tooltip/components/Tooltip.tsx b/react/features/base/tooltip/components/Tooltip.tsx index a2632ec755..497223ac2c 100644 --- a/react/features/base/tooltip/components/Tooltip.tsx +++ b/react/features/base/tooltip/components/Tooltip.tsx @@ -16,7 +16,7 @@ const ANIMATION_DURATION = 0.2; interface IProps { children: ReactElement; containerClassName?: string; - content: string; + content: string | ReactElement; position?: TOOLTIP_POSITION; } diff --git a/react/features/base/ui/Tokens.ts b/react/features/base/ui/Tokens.ts index a7b366cc59..159707becb 100644 --- a/react/features/base/ui/Tokens.ts +++ b/react/features/base/ui/Tokens.ts @@ -167,6 +167,7 @@ export const font = { export const shape = { borderRadius: 6, + circleRadius: 50, boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)' }; diff --git a/react/features/base/util/contants.ts b/react/features/base/util/contants.ts new file mode 100644 index 0000000000..3732139031 --- /dev/null +++ b/react/features/base/util/contants.ts @@ -0,0 +1 @@ +export const SECURITY_URL = 'https://jitsi.org/security/'; diff --git a/react/features/base/util/getUnsafeRoomText.native.ts b/react/features/base/util/getUnsafeRoomText.native.ts new file mode 100644 index 0000000000..b37e0faf86 --- /dev/null +++ b/react/features/base/util/getUnsafeRoomText.native.ts @@ -0,0 +1,31 @@ +import React from 'react'; +import { Text } from 'react-native'; + + +import { IReduxState } from '../../app/types'; +import Link from '../react/components/native/Link'; +import BaseTheme from '../ui/components/BaseTheme.native'; + +import { SECURITY_URL } from './contants'; + +/** + * Gets the unsafe room text for the given context. + * + * @param {IReduxState} state - The redux state. + * @param {Function} t - The translation function. + * @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining. + * @returns {Text} + */ +export default function getUnsafeRoomText(state: IReduxState, t: Function, context: 'meeting' | 'prejoin' | 'welcome') { + const securityUrl = state['features/base/config'].legalUrls?.security ?? SECURITY_URL; + const link = React.createElement(Link, { + url: securityUrl, + children: 'here', + style: { color: BaseTheme.palette.action01 } }); + + const options = { + recommendAction: t(`security.unsafeRoomActions.${context}`) + }; + + return React.createElement(Text, { children: [ t('security.insecureRoomNameWarningNative', options), link, '.' ] }); +} diff --git a/react/features/base/util/getUnsafeRoomText.web.ts b/react/features/base/util/getUnsafeRoomText.web.ts new file mode 100644 index 0000000000..3c2abb2bd1 --- /dev/null +++ b/react/features/base/util/getUnsafeRoomText.web.ts @@ -0,0 +1,20 @@ +import { translateToHTML } from '../i18n/functions'; + +import { SECURITY_URL } from './contants'; + +/** + * Gets the unsafe room text for the given context. + * + * @param {Function} t - The translation function. + * @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining. + * @returns {string} + */ +export default function getUnsafeRoomText(t: Function, context: 'meeting' | 'prejoin' | 'welcome') { + const securityUrl = APP.store.getState()['features/base/config'].legalUrls?.security ?? SECURITY_URL; + const options = { + recommendAction: t(`security.unsafeRoomActions.${context}`), + securityUrl + }; + + return translateToHTML(t, 'security.insecureRoomNameWarningWeb', options); +} diff --git a/react/features/conference/components/native/InsecureRoomNameExpandedLabel.ts b/react/features/conference/components/native/InsecureRoomNameExpandedLabel.tsx similarity index 50% rename from react/features/conference/components/native/InsecureRoomNameExpandedLabel.ts rename to react/features/conference/components/native/InsecureRoomNameExpandedLabel.tsx index 092150c1a1..8d4e781ad3 100644 --- a/react/features/conference/components/native/InsecureRoomNameExpandedLabel.ts +++ b/react/features/conference/components/native/InsecureRoomNameExpandedLabel.tsx @@ -1,17 +1,22 @@ import { WithTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { IReduxState } from '../../../app/types'; import { translate } from '../../../base/i18n/functions'; import ExpandedLabel, { IProps as AbstractProps } from '../../../base/label/components/native/ExpandedLabel'; +import getUnsafeRoomText from '../../../base/util/getUnsafeRoomText.native'; import { INSECURE_ROOM_NAME_LABEL_COLOR } from './styles'; -type Props = AbstractProps & WithTranslation; +interface IProps extends AbstractProps, WithTranslation { + getUnsafeRoomTextFn: Function; +} /** * A react {@code Component} that implements an expanded label as tooltip-like * component to explain the meaning of the {@code InsecureRoomNameExpandedLabel}. */ -class InsecureRoomNameExpandedLabel extends ExpandedLabel { +class InsecureRoomNameExpandedLabel extends ExpandedLabel { /** * Returns the color this expanded label should be rendered with. * @@ -27,8 +32,20 @@ class InsecureRoomNameExpandedLabel extends ExpandedLabel { * @returns {string} */ _getLabel() { - return this.props.t('security.insecureRoomNameWarning'); + return this.props.getUnsafeRoomTextFn(this.props.t); } } -export default translate(InsecureRoomNameExpandedLabel); +/** + * Maps part of the Redux state to the props of this component. + * + * @param {Object} state - The Redux state. + * @returns {IProps} + */ +function _mapStateToProps(state: IReduxState) { + return { + getUnsafeRoomTextFn: (t: Function) => getUnsafeRoomText(state, t, 'meeting') + }; +} + +export default translate(connect(_mapStateToProps)(InsecureRoomNameExpandedLabel)); diff --git a/react/features/conference/components/web/InsecureRoomNameLabel.tsx b/react/features/conference/components/web/InsecureRoomNameLabel.tsx index 0ed47d22b0..0a4b622a47 100644 --- a/react/features/conference/components/web/InsecureRoomNameLabel.tsx +++ b/react/features/conference/components/web/InsecureRoomNameLabel.tsx @@ -6,6 +6,7 @@ import { IconExclamationTriangle } from '../../../base/icons/svg'; import Label from '../../../base/label/components/web/Label'; import { COLORS } from '../../../base/label/constants'; import Tooltip from '../../../base/tooltip/components/Tooltip'; +import getUnsafeRoomText from '../../../base/util/getUnsafeRoomText.web'; import AbstractInsecureRoomNameLabel, { _mapStateToProps } from '../AbstractInsecureRoomNameLabel'; /** @@ -20,7 +21,7 @@ class InsecureRoomNameLabel extends AbstractInsecureRoomNameLabel { _render() { return (