Compare commits

...

15 Commits

Author SHA1 Message Date
Mihaela Dumitru
33e4da32e2 feat(giphy) disable feature from dynamic branding (#12620) 2022-12-08 16:02:31 +02:00
bgrozev
9a8a8ef7ad Bump js-utils to 2.0.5 (filter room names). (#12625)
* Bump js-utils to 2.0.5 (filter room names).
2022-12-07 10:35:42 -06:00
Calin-Teodor
703ed731c8 feat(base/redux): fixed local storage on native 2022-12-07 18:16:53 +02:00
William Liang
83dfb67f23 fix(video-mute) prevent multiple camera track creation 2022-12-07 10:23:20 -05:00
Robert Pintilii
51bdf67cf2 fix(dialog) Fix Dialog on mobile (#12650)
Use JitsiPortal on mobile
2022-12-07 11:27:55 +02:00
damencho
3adbda791c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1548.0.0+8acdeb1d...v1549.0.0+877c4546
2022-12-06 19:10:02 -06:00
Jaya Allamsetty
924bb0e7ff chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1545.0.0+f8e587f7...v1548.0.0+8acdeb1d
2022-12-06 16:31:06 -06:00
tmoldovan8x8
4c9bfe3d4d feat(E2EE) add initial SAS verification UI 2022-12-06 18:29:33 +01:00
Jaya Allamsetty
1139311809 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1543.0.0+c57ac97e...v1545.0.0+f8e587f7
2022-12-06 10:53:20 -05:00
Calinteodor
2ad2e6ff0e feat(polls/web): removed sort options from polls (#12641)
* feat(polls/web): removed sort options from polls
2022-12-05 15:18:24 +02:00
damencho
46cc2e37ae feat: Update lib-jitsi-meet.
Fixes version of binary ljm.
2022-12-02 15:31:33 -06:00
Saúl Ibarra Corretgé
0ebac2ac6d fixup! 2022-12-02 19:05:47 +01:00
Saúl Ibarra Corretgé
90e33ee799 fixup devcontainer 2022-12-02 19:05:47 +01:00
Saúl Ibarra Corretgé
be982ae996 fix(build) use http for GitHub codespaces 2022-12-02 19:05:47 +01:00
Saúl Ibarra Corretgé
56114fe863 Create devcontainer.json 2022-12-02 19:05:47 +01:00
33 changed files with 507 additions and 74 deletions

View File

@@ -0,0 +1,15 @@
{
"name": "Jitsi Meet Dev Container",
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "16"
}
},
"hostRequirements": {
"cpus": 4,
"memory": "8gb",
"storage": "32gb"
},
"postCreateCommand": "bash -i -c 'nvm use && npm install && cp tsconfig.web.json tsconfig.json'"
}

View File

@@ -141,7 +141,7 @@ react/features/sample/
```
The middleware must be imported in `react/features/app/` specifically
in `middlewares.any`, `middlewares.native.js` or `middlewares.web.js` where appropriate.
in `middlewares.any.ts`, `middlewares.native.ts` or `middlewares.web.ts` where appropriate.
Likewise for the reducer.
An `index.js` file must not be provided for exporting actions, action types and

View File

@@ -459,6 +459,11 @@ export default {
*/
_localTracksInitialized: false,
/**
* Flag used to prevent the creation of another local video track in this.muteVideo if one is already in progress.
*/
isCreatingLocalTrack: false,
isSharingScreen: false,
/**
@@ -1028,11 +1033,13 @@ export default {
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
if (!localVideo && !mute) {
if (!localVideo && !mute && !this.isCreatingLocalTrack) {
const maybeShowErrorDialog = error => {
showUI && APP.store.dispatch(notifyCameraError(error));
};
this.isCreatingLocalTrack = true;
// Try to create local video if there wasn't any.
// This handles the case when user joined with no video
// (dismissed screen sharing screen or in audio only mode), but
@@ -1054,6 +1061,9 @@ export default {
logger.debug(`muteVideo: calling useVideoStream for track: ${videoTrack}`);
return this.useVideoStream(videoTrack);
})
.finally(() => {
this.isCreatingLocalTrack = false;
});
} else {
// FIXME show error dialog if it fails (should be handled by react)

View File

@@ -121,13 +121,6 @@ ol.poll-result-list {
display: flex;
}
.poll-dragged {
opacity: 0.5;
* {
cursor: grabbing !important;
}
}
.poll-question {
font-size: 16px;
font-weight: 600;

View File

@@ -147,6 +147,7 @@
"bridgeCount": "Server count: ",
"codecs": "Codecs (A/V): ",
"connectedTo": "Connected to:",
"e2eeVerified": "E2EE verified:",
"framerate": "Frame rate:",
"less": "Show less",
"localaddress": "Local address:",
@@ -408,6 +409,10 @@
"user": "User",
"userIdentifier": "User identifier",
"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",
"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.",
@@ -1297,6 +1302,7 @@
"show": "Show on stage",
"showSelfView": "Show self view",
"unpinFromStage": "Unpin",
"verify": "Verify participant",
"videoMuted": "Camera disabled",
"videomute": "Participant has stopped the camera"
},

24
package-lock.json generated
View File

@@ -28,7 +28,7 @@
"@giphy/react-native-sdk": "1.7.0",
"@hapi/bourne": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
"@jitsi/js-utils": "2.0.4",
"@jitsi/js-utils": "2.0.5",
"@jitsi/logger": "2.0.0",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.0",
@@ -74,7 +74,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1541.0.0+9b34e0f7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -3756,9 +3756,9 @@
}
},
"node_modules/@jitsi/js-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.4.tgz",
"integrity": "sha512-voXa8Y8srv/q3gD9wWOGMPVqOWT4s0n4B/ApkPDAIN8EG/6mpzAfHNMi4BIOQeeo2P0srIdcD6Y/1S/ftjuhYQ==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.5.tgz",
"integrity": "sha512-Aa7lt/sGsDymWnKJtM1RePmR2b2J5TwY3QLv5iOmzMDYR+5RE0NyYc/vKW51JeatDVSkj+LT7kpUDvtJua0rmQ==",
"dependencies": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -13497,8 +13497,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1541.0.0+9b34e0f7/lib-jitsi-meet.tgz",
"integrity": "sha512-A+QkH3v0XzLSxumHC7LHWXLmFHTqrJ/1YCbWFd/eHjDGXVyFCPeazYBpsNdGZMHfEBzG9FLdQCEOxyzBY7yIAA==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
@@ -23182,9 +23182,9 @@
"integrity": "sha512-WFzaH5GCZLA5DTSZ6ReqAz9g53mSgi+211zTC7AFZUYZme5tzKpPg55AKkJZA3ZIRikkbKaEfP/dC4QOH5NMmA=="
},
"@jitsi/js-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.4.tgz",
"integrity": "sha512-voXa8Y8srv/q3gD9wWOGMPVqOWT4s0n4B/ApkPDAIN8EG/6mpzAfHNMi4BIOQeeo2P0srIdcD6Y/1S/ftjuhYQ==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.5.tgz",
"integrity": "sha512-Aa7lt/sGsDymWnKJtM1RePmR2b2J5TwY3QLv5iOmzMDYR+5RE0NyYc/vKW51JeatDVSkj+LT7kpUDvtJua0rmQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -30510,8 +30510,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1541.0.0+9b34e0f7/lib-jitsi-meet.tgz",
"integrity": "sha512-A+QkH3v0XzLSxumHC7LHWXLmFHTqrJ/1YCbWFd/eHjDGXVyFCPeazYBpsNdGZMHfEBzG9FLdQCEOxyzBY7yIAA==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",

View File

@@ -33,7 +33,7 @@
"@giphy/react-native-sdk": "1.7.0",
"@hapi/bourne": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
"@jitsi/js-utils": "2.0.4",
"@jitsi/js-utils": "2.0.5",
"@jitsi/logger": "2.0.0",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.0",
@@ -79,7 +79,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1541.0.0+9b34e0f7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -11,6 +11,7 @@ import '../base/media/middleware';
import '../base/net-info/middleware';
import '../base/participants/middleware';
import '../base/responsive-ui/middleware';
import '../base/redux/middleware';
import '../base/settings/middleware';
import '../base/sounds/middleware';
import '../base/testing/middleware';

View File

@@ -2,7 +2,6 @@ import '../authentication/middleware';
import '../base/i18n/middleware';
import '../base/devices/middleware';
import '../base/media/middleware';
import '../base/redux/middleware';
import '../dynamic-branding/middleware';
import '../e2ee/middleware';
import '../external-api/middleware';

View File

@@ -59,6 +59,7 @@ export interface IJitsiConference {
grantOwner: Function;
isAVModerationSupported: Function;
isCallstatsEnabled: Function;
isE2EEEnabled: Function;
isEndConferenceSupported: Function;
isLobbySupported: Function;
isSIPCallingSupported: Function;
@@ -89,6 +90,7 @@ export interface IJitsiConference {
setReceiverConstraints: Function;
setSenderVideoConstraint: Function;
setSubject: Function;
startVerification: Function;
}
export interface IConferenceState {

View File

@@ -4,6 +4,7 @@ import WaitForOwnerDialog from '../../authentication/components/web/WaitForOwner
import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog';
import DesktopPicker from '../../desktop-picker/components/DesktopPicker';
import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt';
import ParticipantVerificationDialog from '../../e2ee/components/ParticipantVerificationDialog';
import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog';
// @ts-ignore
import FeedbackDialog from '../../feedback/components/FeedbackDialog.web';
@@ -49,7 +50,7 @@ const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNam
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
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.
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);

View File

@@ -13,6 +13,8 @@ export interface IParticipant {
dominantSpeaker?: boolean;
e2eeEnabled?: boolean;
e2eeSupported?: boolean;
e2eeVerificationAvailable?: boolean;
e2eeVerified?: boolean;
email?: string;
fakeParticipant?: FakeParticipant;
features?: {

View File

@@ -27,6 +27,7 @@ const useStyles = makeStyles()(theme => {
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
zIndex: 301,
animation: `${keyframes`
0% {
opacity: 0.4;

View File

@@ -3,6 +3,10 @@ import React, { Component, ComponentType } from 'react';
import { IReduxState } from '../../../../app/types';
import { IReactionEmojiProps } from '../../../../reactions/constants';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { JitsiPortal } from '../../../../toolbox/components/web';
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
import { connect } from '../../../redux/functions';
import DialogTransition from './DialogTransition';
@@ -24,6 +28,11 @@ interface IProps {
*/
_isNewDialog: boolean;
/**
* Whether the overflow drawer should be used.
*/
_overflowDrawer: boolean;
/**
* Array of reactions to be displayed.
*/
@@ -69,7 +78,9 @@ class DialogContainer extends Component<IProps> {
render() {
return this.props._isNewDialog ? (
<DialogTransition>
{this._renderDialogContent()}
{this.props._overflowDrawer
? <JitsiPortal>{this._renderDialogContent()}</JitsiPortal>
: this._renderDialogContent() }
</DialogTransition>
) : (
<ModalTransition>
@@ -90,11 +101,13 @@ class DialogContainer extends Component<IProps> {
function mapStateToProps(state: IReduxState) {
const stateFeaturesBaseDialog = state['features/base/dialog'];
const { reducedUI } = state['features/base/responsive-ui'];
const overflowDrawer = showOverflowDrawer(state);
return {
_component: stateFeaturesBaseDialog.component,
_componentProps: stateFeaturesBaseDialog.componentProps,
_isNewDialog: stateFeaturesBaseDialog.isNewDialog,
_overflowDrawer: overflowDrawer,
_reducedUI: reducedUI
};
}

View File

@@ -189,6 +189,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
codec = { codec }
connectionSummary = { this._getConnectionStatusTip() }
disableShowMoreStats = { this.props._disableShowMoreStats }
e2eeVerified = { this.props._isE2EEVerified }
enableSaveLogs = { this.props._enableSaveLogs }
framerate = { framerate }
isLocalVideo = { this.props._isLocalVideo }
@@ -328,6 +329,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_isE2EEVerified: participant?.e2eeVerified,
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),
_isLocalVideo: participant?.local,
_region: participant?.region,

View File

@@ -73,6 +73,11 @@ interface IProps extends WithTranslation {
*/
disableShowMoreStats: boolean;
/**
* Whether or not the participant was verified.
*/
e2eeVerified: boolean;
/**
* 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
@@ -726,6 +756,7 @@ class ConnectionStatsTable extends Component<IProps> {
{ this._renderResolution() }
{ this._renderFrameRate() }
{ this._renderCodecs() }
{ this._renderE2EEVerified() }
</tbody>
</table>
);

View File

@@ -156,6 +156,7 @@ export interface IDynamicBrandingState {
logoImageUrl: string;
muiBrandedTheme?: boolean;
premeetingBackground: string;
showGiphyIntegration?: boolean;
useDynamicBrandingData: boolean;
virtualBackgrounds: Array<Image>;
}
@@ -178,6 +179,7 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
logoImageUrl,
muiBrandedTheme,
premeetingBackground,
showGiphyIntegration,
virtualBackgrounds
} = action.value;
@@ -193,6 +195,7 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
logoImageUrl,
muiBrandedTheme,
premeetingBackground,
showGiphyIntegration,
customizationFailed: false,
customizationReady: true,
useDynamicBrandingData: true,

View File

@@ -43,3 +43,7 @@ export const SET_MAX_MODE = 'SET_MAX_MODE';
* }
*/
export const SET_MEDIA_ENCRYPTION_KEY = 'SET_MEDIA_ENCRYPTION_KEY';
export const START_VERIFICATION = 'START_VERIFICATION';
export const PARTICIPANT_VERIFIED = 'PARTICIPANT_VERIFIED';

View File

@@ -1,8 +1,10 @@
import {
PARTICIPANT_VERIFIED,
SET_EVERYONE_ENABLED_E2EE,
SET_EVERYONE_SUPPORT_E2EE,
SET_MAX_MODE,
SET_MEDIA_ENCRYPTION_KEY,
START_VERIFICATION,
TOGGLE_E2EE } from './actionTypes';
/**
@@ -80,3 +82,38 @@ export function setMediaEncryptionKey(keyInfo: Object) {
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
};
}

View File

@@ -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)));

View File

@@ -1,7 +1,9 @@
import { IReduxState } from '../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 { 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;
}
/**
* 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);
}

View File

@@ -4,6 +4,8 @@ import { IStore } from '../app/types';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
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 { participantUpdated } from '../base/participants/actions';
import {
@@ -17,13 +19,15 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
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 ParticipantVerificationDialog from './components/ParticipantVerificationDialog';
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID, MAX_MODE } from './constants';
import { isMaxModeReached, isMaxModeThresholdReached } from './functions';
import logger from './logger';
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
/**
* Middleware that captures actions related to E2EE.
*
@@ -239,6 +243,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break;
}
case PARTICIPANT_VERIFIED: {
const { isVerified, pId } = action;
conference?.markParticipantVerified(pId, isVerified);
break;
}
case START_VERIFICATION: {
conference?.startVerification(action.pId);
break;
}
}
return next(action);
@@ -254,6 +270,29 @@ StateListenerRegistry.register(
if (previousConference) {
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
}));
});
});
/**

View File

@@ -20,6 +20,10 @@ export interface IE2EEState {
maxMode: string;
}
export interface ISas {
emoji: Array<string>;
}
/**
* Reduces the Redux actions of the feature features/e2ee.
*/

View File

@@ -68,12 +68,13 @@ export function getGifAPIKey(state: IReduxState) {
export function isGifEnabled(state: IReduxState) {
const { disableThirdPartyRequests } = state['features/base/config'];
const { giphy } = state['features/base/config'];
const showGiphyIntegration = state['features/dynamic-branding']?.showGiphyIntegration !== false;
if (navigator.product === 'ReactNative' && window.JITSI_MEET_LITE_SDK) {
return false;
}
return Boolean(!disableThirdPartyRequests && giphy?.enabled && Boolean(giphy?.sdkKey));
return showGiphyIntegration && Boolean(!disableThirdPartyRequests && giphy?.enabled && Boolean(giphy?.sdkKey));
}
/**

View File

@@ -2,8 +2,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { makeStyles } from 'tss-react/mui';
import Icon from '../../../base/icons/components/Icon';
import { IconBurger } from '../../../base/icons/svg';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import Button from '../../../base/ui/components/web/Button';
@@ -26,7 +24,6 @@ const PollCreate = ({
addAnswer,
answers,
isSubmitDisabled,
moveAnswer,
onSubmit,
question,
removeAnswer,
@@ -142,37 +139,6 @@ const PollCreate = ({
}
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
const [ grabbing, setGrabbing ] = useState(null);
const interchangeHeights = (i: number, j: number) => {
const h = answerInputs.current[i].scrollHeight;
answerInputs.current[i].style.height = `${answerInputs.current[j].scrollHeight}px`;
answerInputs.current[j].style.height = `${h}px`;
};
const onGrab = useCallback((i, ev) => {
if (ev.button !== 0) {
return;
}
setGrabbing(i);
window.addEventListener('mouseup', () => {
setGrabbing(_grabbing => {
requestFocus(_grabbing);
return null;
});
}, { once: true });
}, []);
const onMouseOver = useCallback(i => {
if (grabbing !== null && grabbing !== i) {
interchangeHeights(i, grabbing);
moveAnswer(grabbing, i);
setGrabbing(i);
}
}, []);
const autogrow = (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
const el = ev.target;
@@ -207,9 +173,8 @@ const PollCreate = ({
<ol className = 'poll-answer-field-list'>
{answers.map((answer: any, i: number) =>
(<li
className = { `poll-answer-field${grabbing === i ? ' poll-dragged' : ''}` }
key = { i }
onMouseOver = { () => onMouseOver(i) }>
className = 'poll-answer-field'
key = { i }>
<span className = 'poll-create-label'>
{ t('polls.create.pollOption', { index: i + 1 })}
</span>
@@ -225,13 +190,6 @@ const PollCreate = ({
required = { true }
rows = { 1 }
value = { answer } />
<button
className = 'poll-drag-handle'
onMouseDown = { ev => onGrab(i, ev) }
tabIndex = { -1 }
type = 'button'>
<Icon src = { IconBurger } />
</button>
</div>
{ answers.length > 2

View File

@@ -18,6 +18,7 @@ import { isParticipantAudioMuted } from '../../../base/tracks/functions';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { displayVerification } from '../../../e2ee/functions';
import { setVolume } from '../../../filmstrip/actions.web';
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
import { isForceMuted } from '../../../participants-pane/functions';
@@ -29,6 +30,7 @@ import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
// @ts-ignore
import SendToRoomButton from './SendToRoomButton';
import VerifyParticipantButton from './VerifyParticipantButton';
import {
AskToUnmuteButton,
@@ -150,6 +152,7 @@ const ParticipantContextMenu = ({
const isBreakoutRoom = useSelector(isInBreakoutRoom);
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
const stageFilmstrip = useSelector(isStageFilmstripAvailable);
const shouldDisplayVerification = useSelector((state: IReduxState) => displayVerification(state, participant?.id));
const _currentRoomId = useSelector(getCurrentRoomId);
const _rooms: Array<{ id: string; }> = Object.values(useSelector(getBreakoutRooms));
@@ -223,6 +226,15 @@ const ParticipantContextMenu = ({
participantID = { _getCurrentParticipantId() } />
);
}
if (shouldDisplayVerification) {
buttons2.push(
<VerifyParticipantButton
key = 'verify'
participantID = { _getCurrentParticipantId() } />
);
}
}
if (stageFilmstrip) {

View File

@@ -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)));

View File

@@ -266,7 +266,7 @@ function getDevServerConfig() {
}
}
},
server: 'https',
server: process.env.CODESPACES ? 'http' : 'https',
static: {
directory: process.cwd()
}