feat(E2EE) add initial SAS verification UI
This commit is contained in:
parent
1139311809
commit
4c9bfe3d4d
|
@ -147,6 +147,7 @@
|
||||||
"bridgeCount": "Server count: ",
|
"bridgeCount": "Server count: ",
|
||||||
"codecs": "Codecs (A/V): ",
|
"codecs": "Codecs (A/V): ",
|
||||||
"connectedTo": "Connected to:",
|
"connectedTo": "Connected to:",
|
||||||
|
"e2eeVerified": "E2EE verified:",
|
||||||
"framerate": "Frame rate:",
|
"framerate": "Frame rate:",
|
||||||
"less": "Show less",
|
"less": "Show less",
|
||||||
"localaddress": "Local address:",
|
"localaddress": "Local address:",
|
||||||
|
@ -408,6 +409,10 @@
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"userIdentifier": "User identifier",
|
"userIdentifier": "User identifier",
|
||||||
"userPassword": "User password",
|
"userPassword": "User password",
|
||||||
|
"verifyParticipantConfirm": "They match",
|
||||||
|
"verifyParticipantDismiss": "They do not match",
|
||||||
|
"verifyParticipantQuestion": "EXPERIMENTAL: Ask participant {{participantName}} if they see the same content, in the same order.",
|
||||||
|
"verifyParticipantTitle": "User verification",
|
||||||
"videoLink": "Video link",
|
"videoLink": "Video link",
|
||||||
"viewUpgradeOptions": "View upgrade options",
|
"viewUpgradeOptions": "View upgrade options",
|
||||||
"viewUpgradeOptionsContent": "To get unlimited access to premium features like recording, transcriptions, RTMP Streaming & more, you'll need to upgrade your plan.",
|
"viewUpgradeOptionsContent": "To get unlimited access to premium features like recording, transcriptions, RTMP Streaming & more, you'll need to upgrade your plan.",
|
||||||
|
@ -1297,6 +1302,7 @@
|
||||||
"show": "Show on stage",
|
"show": "Show on stage",
|
||||||
"showSelfView": "Show self view",
|
"showSelfView": "Show self view",
|
||||||
"unpinFromStage": "Unpin",
|
"unpinFromStage": "Unpin",
|
||||||
|
"verify": "Verify participant",
|
||||||
"videoMuted": "Camera disabled",
|
"videoMuted": "Camera disabled",
|
||||||
"videomute": "Participant has stopped the camera"
|
"videomute": "Participant has stopped the camera"
|
||||||
},
|
},
|
||||||
|
|
|
@ -59,6 +59,7 @@ export interface IJitsiConference {
|
||||||
grantOwner: Function;
|
grantOwner: Function;
|
||||||
isAVModerationSupported: Function;
|
isAVModerationSupported: Function;
|
||||||
isCallstatsEnabled: Function;
|
isCallstatsEnabled: Function;
|
||||||
|
isE2EEEnabled: Function;
|
||||||
isEndConferenceSupported: Function;
|
isEndConferenceSupported: Function;
|
||||||
isLobbySupported: Function;
|
isLobbySupported: Function;
|
||||||
isSIPCallingSupported: Function;
|
isSIPCallingSupported: Function;
|
||||||
|
@ -89,6 +90,7 @@ export interface IJitsiConference {
|
||||||
setReceiverConstraints: Function;
|
setReceiverConstraints: Function;
|
||||||
setSenderVideoConstraint: Function;
|
setSenderVideoConstraint: Function;
|
||||||
setSubject: Function;
|
setSubject: Function;
|
||||||
|
startVerification: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IConferenceState {
|
export interface IConferenceState {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import WaitForOwnerDialog from '../../authentication/components/web/WaitForOwner
|
||||||
import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog';
|
import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog';
|
||||||
import DesktopPicker from '../../desktop-picker/components/DesktopPicker';
|
import DesktopPicker from '../../desktop-picker/components/DesktopPicker';
|
||||||
import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt';
|
import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt';
|
||||||
|
import ParticipantVerificationDialog from '../../e2ee/components/ParticipantVerificationDialog';
|
||||||
import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog';
|
import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import FeedbackDialog from '../../feedback/components/FeedbackDialog.web';
|
import FeedbackDialog from '../../feedback/components/FeedbackDialog.web';
|
||||||
|
@ -49,7 +50,7 @@ const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNam
|
||||||
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
|
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
|
||||||
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
|
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
|
||||||
VirtualBackgroundDialog, LoginDialog, WaitForOwnerDialog, DesktopPicker, RemoteControlAuthorizationDialog,
|
VirtualBackgroundDialog, LoginDialog, WaitForOwnerDialog, DesktopPicker, RemoteControlAuthorizationDialog,
|
||||||
LogoutDialog, SalesforceLinkDialog ];
|
LogoutDialog, SalesforceLinkDialog, ParticipantVerificationDialog ];
|
||||||
|
|
||||||
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
|
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
|
||||||
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);
|
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);
|
||||||
|
|
|
@ -13,6 +13,8 @@ export interface IParticipant {
|
||||||
dominantSpeaker?: boolean;
|
dominantSpeaker?: boolean;
|
||||||
e2eeEnabled?: boolean;
|
e2eeEnabled?: boolean;
|
||||||
e2eeSupported?: boolean;
|
e2eeSupported?: boolean;
|
||||||
|
e2eeVerificationAvailable?: boolean;
|
||||||
|
e2eeVerified?: boolean;
|
||||||
email?: string;
|
email?: string;
|
||||||
fakeParticipant?: FakeParticipant;
|
fakeParticipant?: FakeParticipant;
|
||||||
features?: {
|
features?: {
|
||||||
|
|
|
@ -189,6 +189,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||||
codec = { codec }
|
codec = { codec }
|
||||||
connectionSummary = { this._getConnectionStatusTip() }
|
connectionSummary = { this._getConnectionStatusTip() }
|
||||||
disableShowMoreStats = { this.props._disableShowMoreStats }
|
disableShowMoreStats = { this.props._disableShowMoreStats }
|
||||||
|
e2eeVerified = { this.props._isE2EEVerified }
|
||||||
enableSaveLogs = { this.props._enableSaveLogs }
|
enableSaveLogs = { this.props._enableSaveLogs }
|
||||||
framerate = { framerate }
|
framerate = { framerate }
|
||||||
isLocalVideo = { this.props._isLocalVideo }
|
isLocalVideo = { this.props._isLocalVideo }
|
||||||
|
@ -328,6 +329,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||||
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
||||||
_isConnectionStatusInactive,
|
_isConnectionStatusInactive,
|
||||||
_isConnectionStatusInterrupted,
|
_isConnectionStatusInterrupted,
|
||||||
|
_isE2EEVerified: participant?.e2eeVerified,
|
||||||
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),
|
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),
|
||||||
_isLocalVideo: participant?.local,
|
_isLocalVideo: participant?.local,
|
||||||
_region: participant?.region,
|
_region: participant?.region,
|
||||||
|
|
|
@ -73,6 +73,11 @@ interface IProps extends WithTranslation {
|
||||||
*/
|
*/
|
||||||
disableShowMoreStats: boolean;
|
disableShowMoreStats: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the participant was verified.
|
||||||
|
*/
|
||||||
|
e2eeVerified: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not should display the "Save Logs" link.
|
* Whether or not should display the "Save Logs" link.
|
||||||
*/
|
*/
|
||||||
|
@ -486,6 +491,31 @@ class ConnectionStatsTable extends Component<IProps> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a a table row as a ReactElement for displaying e2ee verication status, if present.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderE2EEVerified() {
|
||||||
|
const { e2eeVerified, t } = this.props;
|
||||||
|
|
||||||
|
if (e2eeVerified === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = e2eeVerified ? '\u{2705}' : '\u{274C}';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span>{ t('connectionindicator.e2eeVerified') }</span>
|
||||||
|
</td>
|
||||||
|
<td>{ status }</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a table row as a ReactElement for displaying a summary message
|
* Creates a table row as a ReactElement for displaying a summary message
|
||||||
|
@ -726,6 +756,7 @@ class ConnectionStatsTable extends Component<IProps> {
|
||||||
{ this._renderResolution() }
|
{ this._renderResolution() }
|
||||||
{ this._renderFrameRate() }
|
{ this._renderFrameRate() }
|
||||||
{ this._renderCodecs() }
|
{ this._renderCodecs() }
|
||||||
|
{ this._renderE2EEVerified() }
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,3 +43,7 @@ export const SET_MAX_MODE = 'SET_MAX_MODE';
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const SET_MEDIA_ENCRYPTION_KEY = 'SET_MEDIA_ENCRYPTION_KEY';
|
export const SET_MEDIA_ENCRYPTION_KEY = 'SET_MEDIA_ENCRYPTION_KEY';
|
||||||
|
|
||||||
|
export const START_VERIFICATION = 'START_VERIFICATION';
|
||||||
|
|
||||||
|
export const PARTICIPANT_VERIFIED = 'PARTICIPANT_VERIFIED';
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import {
|
import {
|
||||||
|
PARTICIPANT_VERIFIED,
|
||||||
SET_EVERYONE_ENABLED_E2EE,
|
SET_EVERYONE_ENABLED_E2EE,
|
||||||
SET_EVERYONE_SUPPORT_E2EE,
|
SET_EVERYONE_SUPPORT_E2EE,
|
||||||
SET_MAX_MODE,
|
SET_MAX_MODE,
|
||||||
SET_MEDIA_ENCRYPTION_KEY,
|
SET_MEDIA_ENCRYPTION_KEY,
|
||||||
|
START_VERIFICATION,
|
||||||
TOGGLE_E2EE } from './actionTypes';
|
TOGGLE_E2EE } from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,3 +82,38 @@ export function setMediaEncryptionKey(keyInfo: Object) {
|
||||||
keyInfo
|
keyInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to start participant e2ee verficiation process.
|
||||||
|
*
|
||||||
|
* @param {string} pId - The participant id.
|
||||||
|
* @returns {{
|
||||||
|
* type: START_VERIFICATION,
|
||||||
|
* pId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function startVerification(pId: string) {
|
||||||
|
return {
|
||||||
|
type: START_VERIFICATION,
|
||||||
|
pId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to set participant e2ee verification status.
|
||||||
|
*
|
||||||
|
* @param {string} pId - The participant id.
|
||||||
|
* @param {boolean} isVerified - The verifcation status.
|
||||||
|
* @returns {{
|
||||||
|
* type: PARTICIPANT_VERIFIED,
|
||||||
|
* pId: string,
|
||||||
|
* isVerified: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function participantVerified(pId: string, isVerified: boolean) {
|
||||||
|
return {
|
||||||
|
type: PARTICIPANT_VERIFIED,
|
||||||
|
pId,
|
||||||
|
isVerified
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
import { withStyles } from '@mui/styles';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { IReduxState, IStore } from '../../app/types';
|
||||||
|
import { translate } from '../../base/i18n/functions';
|
||||||
|
import { getParticipantById } from '../../base/participants/functions';
|
||||||
|
import { connect } from '../../base/redux/functions';
|
||||||
|
import Dialog from '../../base/ui/components/web/Dialog';
|
||||||
|
import { participantVerified } from '../actions';
|
||||||
|
import { ISas } from '../reducer';
|
||||||
|
|
||||||
|
interface IProps extends WithTranslation {
|
||||||
|
classes: any;
|
||||||
|
decimal: string;
|
||||||
|
dispatch: IStore['dispatch'];
|
||||||
|
emoji: string;
|
||||||
|
pId: string;
|
||||||
|
participantName: string;
|
||||||
|
sas: ISas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the styles for the component.
|
||||||
|
*
|
||||||
|
* @param {Object} theme - The current UI theme.
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const styles = () => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
margin: '16px'
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: '16px'
|
||||||
|
},
|
||||||
|
emoji: {
|
||||||
|
fontSize: '40px',
|
||||||
|
margin: '12px'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for the dialog displayed for E2EE sas verification.
|
||||||
|
*/
|
||||||
|
export class ParticipantVerificationDialog extends Component<IProps> {
|
||||||
|
/**
|
||||||
|
* Instantiates a new instance.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._onConfirmed = this._onConfirmed.bind(this);
|
||||||
|
this._onDismissed = this._onDismissed.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { emoji } = this.props.sas;
|
||||||
|
const { participantName } = this.props;
|
||||||
|
|
||||||
|
const { classes, t } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
cancel = {{ translationKey: 'dialog.verifyParticipantDismiss' }}
|
||||||
|
ok = {{ translationKey: 'dialog.verifyParticipantConfirm' }}
|
||||||
|
onCancel = { this._onDismissed }
|
||||||
|
onSubmit = { this._onConfirmed }
|
||||||
|
titleKey = 'dialog.verifyParticipantTitle'>
|
||||||
|
<div>
|
||||||
|
{ t('dialog.verifyParticipantQuestion', { participantName }) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className = { classes.container }>
|
||||||
|
<div className = { classes.row }>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{emoji.slice(0, 4).map((e: Array<string>) =>
|
||||||
|
(<div
|
||||||
|
className = { classes.item }
|
||||||
|
key = { e.toString() }>
|
||||||
|
<div className = { classes.emoji }>{ e[0] }</div>
|
||||||
|
<div>{ e[1].charAt(0).toUpperCase() + e[1].slice(1) }</div>
|
||||||
|
</div>))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className = { classes.row }>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{emoji.slice(4, 7).map((e: Array<string>) =>
|
||||||
|
(<div
|
||||||
|
className = { classes.item }
|
||||||
|
key = { e.toString() }>
|
||||||
|
<div className = { classes.emoji }>{ e[0] } </div>
|
||||||
|
<div>{ e[1].charAt(0).toUpperCase() + e[1].slice(1) }</div>
|
||||||
|
</div>))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies this ParticipantVerificationDialog that it has been dismissed by cancel.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onDismissed() {
|
||||||
|
this.props.dispatch(participantVerified(this.props.pId, false));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies this ParticipantVerificationDialog that it has been dismissed with confirmation.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onConfirmed() {
|
||||||
|
this.props.dispatch(participantVerified(this.props.pId, true));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps part of the Redux store to the props of this component.
|
||||||
|
*
|
||||||
|
* @param {IReduxState} state - The Redux state.
|
||||||
|
* @param {IProps} ownProps - The own props of the component.
|
||||||
|
* @returns {IProps}
|
||||||
|
*/
|
||||||
|
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||||
|
const participant = getParticipantById(state, ownProps.pId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sas: ownProps.sas,
|
||||||
|
pId: ownProps.pId,
|
||||||
|
participantName: participant?.name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
withStyles(styles)(ParticipantVerificationDialog)));
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { IReduxState } from '../app/types';
|
||||||
import { IStateful } from '../base/app/types';
|
import { IStateful } from '../base/app/types';
|
||||||
import { getParticipantCount } from '../base/participants/functions';
|
import { getParticipantById, getParticipantCount } from '../base/participants/functions';
|
||||||
import { toState } from '../base/redux/functions';
|
import { toState } from '../base/redux/functions';
|
||||||
|
|
||||||
|
|
||||||
import { MAX_MODE_LIMIT, MAX_MODE_THRESHOLD } from './constants';
|
import { MAX_MODE_LIMIT, MAX_MODE_THRESHOLD } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,3 +57,19 @@ export function isMaxModeThresholdReached(stateful: IStateful) {
|
||||||
|
|
||||||
return participantCount >= MAX_MODE_LIMIT + MAX_MODE_THRESHOLD;
|
return participantCount >= MAX_MODE_LIMIT + MAX_MODE_THRESHOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether e2ee is enabled by the backend.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @param {string} pId - The participant id.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function displayVerification(state: IReduxState, pId: string) {
|
||||||
|
const { conference } = state['features/base/conference'];
|
||||||
|
const participant = getParticipantById(state, pId);
|
||||||
|
|
||||||
|
return Boolean(conference?.isE2EEEnabled()
|
||||||
|
&& participant?.e2eeVerificationAvailable
|
||||||
|
&& participant?.e2eeVerified === undefined);
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { IStore } from '../app/types';
|
||||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||||
import { getCurrentConference } from '../base/conference/functions';
|
import { getCurrentConference } from '../base/conference/functions';
|
||||||
|
import { openDialog } from '../base/dialog/actions';
|
||||||
|
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
||||||
import { participantUpdated } from '../base/participants/actions';
|
import { participantUpdated } from '../base/participants/actions';
|
||||||
import {
|
import {
|
||||||
|
@ -17,13 +19,15 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||||
|
|
||||||
import { SET_MEDIA_ENCRYPTION_KEY, TOGGLE_E2EE } from './actionTypes';
|
import { PARTICIPANT_VERIFIED, SET_MEDIA_ENCRYPTION_KEY, START_VERIFICATION, TOGGLE_E2EE } from './actionTypes';
|
||||||
import { setE2EEMaxMode, setEveryoneEnabledE2EE, setEveryoneSupportE2EE, toggleE2EE } from './actions';
|
import { setE2EEMaxMode, setEveryoneEnabledE2EE, setEveryoneSupportE2EE, toggleE2EE } from './actions';
|
||||||
|
import ParticipantVerificationDialog from './components/ParticipantVerificationDialog';
|
||||||
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID, MAX_MODE } from './constants';
|
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID, MAX_MODE } from './constants';
|
||||||
import { isMaxModeReached, isMaxModeThresholdReached } from './functions';
|
import { isMaxModeReached, isMaxModeThresholdReached } from './functions';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
|
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware that captures actions related to E2EE.
|
* Middleware that captures actions related to E2EE.
|
||||||
*
|
*
|
||||||
|
@ -239,6 +243,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PARTICIPANT_VERIFIED: {
|
||||||
|
const { isVerified, pId } = action;
|
||||||
|
|
||||||
|
conference?.markParticipantVerified(pId, isVerified);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case START_VERIFICATION: {
|
||||||
|
conference?.startVerification(action.pId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
@ -254,6 +270,29 @@ StateListenerRegistry.register(
|
||||||
if (previousConference) {
|
if (previousConference) {
|
||||||
dispatch(toggleE2EE(false));
|
dispatch(toggleE2EE(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_AVAILABLE, (pId: string) => {
|
||||||
|
dispatch(participantUpdated({
|
||||||
|
e2eeVerificationAvailable: true,
|
||||||
|
id: pId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
|
||||||
|
dispatch(openDialog(ParticipantVerificationDialog, { pId,
|
||||||
|
sas }));
|
||||||
|
});
|
||||||
|
|
||||||
|
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_COMPLETED,
|
||||||
|
(pId: string, success: boolean, message: string) => {
|
||||||
|
if (message) {
|
||||||
|
logger.warn('E2EE_VERIFICATION_COMPLETED warning', message);
|
||||||
|
}
|
||||||
|
dispatch(participantUpdated({
|
||||||
|
e2eeVerified: success,
|
||||||
|
id: pId
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,10 @@ export interface IE2EEState {
|
||||||
maxMode: string;
|
maxMode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISas {
|
||||||
|
emoji: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the Redux actions of the feature features/e2ee.
|
* Reduces the Redux actions of the feature features/e2ee.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { isParticipantAudioMuted } from '../../../base/tracks/functions';
|
||||||
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
||||||
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
|
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
|
||||||
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||||
|
import { displayVerification } from '../../../e2ee/functions';
|
||||||
import { setVolume } from '../../../filmstrip/actions.web';
|
import { setVolume } from '../../../filmstrip/actions.web';
|
||||||
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
|
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
|
||||||
import { isForceMuted } from '../../../participants-pane/functions';
|
import { isForceMuted } from '../../../participants-pane/functions';
|
||||||
|
@ -29,6 +30,7 @@ import { showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||||
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
|
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import SendToRoomButton from './SendToRoomButton';
|
import SendToRoomButton from './SendToRoomButton';
|
||||||
|
import VerifyParticipantButton from './VerifyParticipantButton';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AskToUnmuteButton,
|
AskToUnmuteButton,
|
||||||
|
@ -150,6 +152,7 @@ const ParticipantContextMenu = ({
|
||||||
const isBreakoutRoom = useSelector(isInBreakoutRoom);
|
const isBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||||
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
|
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
|
||||||
const stageFilmstrip = useSelector(isStageFilmstripAvailable);
|
const stageFilmstrip = useSelector(isStageFilmstripAvailable);
|
||||||
|
const shouldDisplayVerification = useSelector((state: IReduxState) => displayVerification(state, participant?.id));
|
||||||
|
|
||||||
const _currentRoomId = useSelector(getCurrentRoomId);
|
const _currentRoomId = useSelector(getCurrentRoomId);
|
||||||
const _rooms: Array<{ id: string; }> = Object.values(useSelector(getBreakoutRooms));
|
const _rooms: Array<{ id: string; }> = Object.values(useSelector(getBreakoutRooms));
|
||||||
|
@ -223,6 +226,15 @@ const ParticipantContextMenu = ({
|
||||||
participantID = { _getCurrentParticipantId() } />
|
participantID = { _getCurrentParticipantId() } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldDisplayVerification) {
|
||||||
|
buttons2.push(
|
||||||
|
<VerifyParticipantButton
|
||||||
|
key = 'verify'
|
||||||
|
participantID = { _getCurrentParticipantId() } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stageFilmstrip) {
|
if (stageFilmstrip) {
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/* eslint-disable lines-around-comment */
|
||||||
|
import { withStyles } from '@mui/styles';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { IReduxState } from '../../../app/types';
|
||||||
|
import { translate } from '../../../base/i18n/functions';
|
||||||
|
import { IconCheck } from '../../../base/icons/svg';
|
||||||
|
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||||
|
import { startVerification } from '../../../e2ee/actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of
|
||||||
|
* {@link VerifyParticipantButton}.
|
||||||
|
*/
|
||||||
|
interface IProps extends WithTranslation {
|
||||||
|
/**
|
||||||
|
* The redux {@code dispatch} function.
|
||||||
|
*/
|
||||||
|
dispatch: Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the participant that this button is supposed to verified.
|
||||||
|
*/
|
||||||
|
participantID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = () => {
|
||||||
|
return {
|
||||||
|
triggerButton: {
|
||||||
|
padding: '3px !important',
|
||||||
|
borderRadius: '4px'
|
||||||
|
},
|
||||||
|
|
||||||
|
contextMenu: {
|
||||||
|
position: 'relative' as const,
|
||||||
|
marginTop: 0,
|
||||||
|
right: 'auto',
|
||||||
|
marginRight: '4px',
|
||||||
|
marginBottom: '4px'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React {@code Component} for displaying an icon associated with opening the
|
||||||
|
* the {@code VideoMenu}.
|
||||||
|
*
|
||||||
|
* @augments {Component}
|
||||||
|
*/
|
||||||
|
class VerifyParticipantButton extends Component<IProps> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new {@code Component}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._handleClick = this._handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { participantID, t } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenuItem
|
||||||
|
accessibilityLabel = { t('videothumbnail.verify') }
|
||||||
|
className = 'verifylink'
|
||||||
|
icon = { IconCheck }
|
||||||
|
id = { `verifylink_${participantID}` }
|
||||||
|
// eslint-disable-next-line react/jsx-handler-names
|
||||||
|
onClick = { this._handleClick }
|
||||||
|
text = { t('videothumbnail.verify') } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking / pressing the button, and starts the participant verification process.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_handleClick() {
|
||||||
|
const { dispatch, participantID } = this.props;
|
||||||
|
|
||||||
|
dispatch(startVerification(participantID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code RemoteVideoMenuTriggerButton}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @param {Object} ownProps - The own props of the component.
|
||||||
|
* @private
|
||||||
|
* @returns {IProps}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
||||||
|
const { participantID } = ownProps;
|
||||||
|
|
||||||
|
return {
|
||||||
|
_participantID: participantID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(withStyles(styles)(VerifyParticipantButton)));
|
Loading…
Reference in New Issue