2022-08-04 08:26:13 +00:00
|
|
|
/* eslint-disable react/jsx-no-bind */
|
2022-10-04 10:52:37 +00:00
|
|
|
import React, { useRef, useState } from 'react';
|
2022-08-04 08:26:13 +00:00
|
|
|
import { WithTranslation } from 'react-i18next';
|
2020-04-01 07:47:51 +00:00
|
|
|
|
2022-08-04 08:26:13 +00:00
|
|
|
import { translate } from '../../../../base/i18n/functions';
|
2022-10-06 08:11:06 +00:00
|
|
|
import { copyText } from '../../../../base/util/copyText.web';
|
2022-10-04 10:52:37 +00:00
|
|
|
import { LOCKED_LOCALLY } from '../../../../room-lock/constants';
|
2022-01-04 11:21:00 +00:00
|
|
|
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants';
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
import PasswordForm from './PasswordForm';
|
2022-10-20 09:11:27 +00:00
|
|
|
import { INotifyClick } from './SecurityDialog';
|
2020-04-01 07:47:51 +00:00
|
|
|
|
2022-01-21 12:58:54 +00:00
|
|
|
const DIGITS_ONLY = /^\d+$/;
|
2022-01-04 11:21:00 +00:00
|
|
|
const KEY = 'add-passcode';
|
|
|
|
|
2022-10-20 09:11:27 +00:00
|
|
|
interface IProps extends WithTranslation {
|
2020-04-01 07:47:51 +00:00
|
|
|
|
2022-01-04 11:21:00 +00:00
|
|
|
/**
|
|
|
|
* Toolbar buttons which have their click exposed through the API.
|
|
|
|
*/
|
2022-10-20 09:11:27 +00:00
|
|
|
buttonsWithNotifyClick: Array<string | INotifyClick>;
|
2022-01-04 11:21:00 +00:00
|
|
|
|
2020-04-01 07:47:51 +00:00
|
|
|
/**
|
|
|
|
* Whether or not the current user can modify the current password.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
canEditPassword: boolean;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The JitsiConference for which to display a lock state and change the
|
|
|
|
* password.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
conference: any;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The value for how the conference is locked (or undefined if not locked)
|
|
|
|
* as defined by room-lock constants.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
locked: string;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The current known password for the JitsiConference.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
password: string;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not to show the password in editing mode.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
passwordEditEnabled: boolean;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of digits to be used in the password.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
passwordNumberOfDigits?: number;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Action that sets the conference password.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
setPassword: Function;
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Method that sets whether the password editing is enabled or not.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
setPasswordEditEnabled: Function;
|
2022-08-04 08:26:13 +00:00
|
|
|
}
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Component that handles the password manipulation from the invite dialog.
|
|
|
|
*
|
|
|
|
* @returns {React$Element<any>}
|
|
|
|
*/
|
|
|
|
function PasswordSection({
|
2022-01-04 11:21:00 +00:00
|
|
|
buttonsWithNotifyClick,
|
2020-04-01 07:47:51 +00:00
|
|
|
canEditPassword,
|
|
|
|
conference,
|
|
|
|
locked,
|
|
|
|
password,
|
|
|
|
passwordEditEnabled,
|
|
|
|
passwordNumberOfDigits,
|
|
|
|
setPassword,
|
|
|
|
setPasswordEditEnabled,
|
2022-10-20 09:11:27 +00:00
|
|
|
t }: IProps) {
|
2020-04-01 07:47:51 +00:00
|
|
|
|
2022-08-04 08:26:13 +00:00
|
|
|
const formRef = useRef<HTMLDivElement>(null);
|
2022-10-04 10:52:37 +00:00
|
|
|
const [ passwordVisible, setPasswordVisible ] = useState(false);
|
2020-04-01 07:47:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to set a password on the current JitsiConference.
|
|
|
|
*
|
|
|
|
* @param {string} enteredPassword - The new password to be used to lock the
|
|
|
|
* current JitsiConference.
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-08-04 08:26:13 +00:00
|
|
|
function onPasswordSubmit(enteredPassword: string) {
|
2022-01-21 12:58:54 +00:00
|
|
|
if (enteredPassword && passwordNumberOfDigits && !DIGITS_ONLY.test(enteredPassword)) {
|
|
|
|
// Don't set the password.
|
|
|
|
return;
|
|
|
|
}
|
2020-04-01 07:47:51 +00:00
|
|
|
setPassword(conference, conference.lock, enteredPassword);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles whether or not the password should currently be shown as being
|
|
|
|
* edited locally.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onTogglePasswordEditState() {
|
2022-01-04 11:21:00 +00:00
|
|
|
if (typeof APP === 'undefined' || !buttonsWithNotifyClick?.length) {
|
|
|
|
setPasswordEditEnabled(!passwordEditEnabled);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let notifyMode;
|
|
|
|
const notify = buttonsWithNotifyClick.find(
|
2022-10-20 09:11:27 +00:00
|
|
|
(btn: string | INotifyClick) =>
|
2022-01-04 11:21:00 +00:00
|
|
|
(typeof btn === 'string' && btn === KEY)
|
|
|
|
|| (typeof btn === 'object' && btn.key === KEY)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (notify) {
|
|
|
|
notifyMode = typeof notify === 'string' || notify.preventExecution
|
|
|
|
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
|
|
|
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
|
|
|
APP.API.notifyToolbarButtonClicked(
|
|
|
|
KEY, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (notifyMode === NOTIFY_CLICK_MODE.ONLY_NOTIFY) {
|
|
|
|
setPasswordEditEnabled(!passwordEditEnabled);
|
|
|
|
}
|
2020-04-01 07:47:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method to remotely submit the password from outside of the password form.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordSave() {
|
|
|
|
if (formRef.current) {
|
2022-08-04 08:26:13 +00:00
|
|
|
// @ts-ignore
|
2022-01-21 12:58:54 +00:00
|
|
|
const { value } = formRef.current.querySelector('div > input');
|
2020-12-18 12:47:00 +00:00
|
|
|
|
|
|
|
if (value) {
|
|
|
|
onPasswordSubmit(value);
|
|
|
|
}
|
2020-04-01 07:47:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to unlock the current JitsiConference.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordRemove() {
|
|
|
|
onPasswordSubmit('');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copies the password to the clipboard.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordCopy() {
|
|
|
|
copyText(password);
|
|
|
|
}
|
|
|
|
|
2021-06-10 12:48:44 +00:00
|
|
|
/**
|
|
|
|
* Toggles whether or not the password should currently be shown as being
|
|
|
|
* edited locally.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-08-04 08:26:13 +00:00
|
|
|
function onTogglePasswordEditStateKeyPressHandler(e: React.KeyboardEvent) {
|
2021-06-10 12:48:44 +00:00
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
onTogglePasswordEditState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method to remotely submit the password from outside of the password form.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-08-04 08:26:13 +00:00
|
|
|
function onPasswordSaveKeyPressHandler(e: React.KeyboardEvent) {
|
2021-06-10 12:48:44 +00:00
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
onPasswordSave();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to unlock the current JitsiConference.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-08-04 08:26:13 +00:00
|
|
|
function onPasswordRemoveKeyPressHandler(e: React.KeyboardEvent) {
|
2021-06-10 12:48:44 +00:00
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
onPasswordRemove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copies the password to the clipboard.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-08-04 08:26:13 +00:00
|
|
|
function onPasswordCopyKeyPressHandler(e: React.KeyboardEvent) {
|
2021-06-10 12:48:44 +00:00
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
onPasswordCopy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 10:52:37 +00:00
|
|
|
/**
|
|
|
|
* Callback invoked to show the current password.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordShow() {
|
|
|
|
setPasswordVisible(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to show the current password.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordShowKeyPressHandler(e: React.KeyboardEvent) {
|
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
setPasswordVisible(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to hide the current password.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordHide() {
|
|
|
|
setPasswordVisible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked to hide the current password.
|
|
|
|
*
|
|
|
|
* @param {Object} e - The key event to handle.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function onPasswordHideKeyPressHandler(e: React.KeyboardEvent) {
|
|
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
|
|
e.preventDefault();
|
|
|
|
setPasswordVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 07:47:51 +00:00
|
|
|
/**
|
|
|
|
* Method that renders the password action(s) based on the current
|
|
|
|
* locked-status of the conference.
|
|
|
|
*
|
|
|
|
* @returns {React$Element<any>}
|
|
|
|
*/
|
|
|
|
function renderPasswordActions() {
|
|
|
|
if (!canEditPassword) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (passwordEditEnabled) {
|
|
|
|
return (
|
|
|
|
<>
|
2021-06-10 12:48:44 +00:00
|
|
|
<a
|
|
|
|
aria-label = { t('dialog.Cancel') }
|
|
|
|
onClick = { onTogglePasswordEditState }
|
|
|
|
onKeyPress = { onTogglePasswordEditStateKeyPressHandler }
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{ t('dialog.Cancel') }</a>
|
|
|
|
<a
|
|
|
|
aria-label = { t('dialog.add') }
|
|
|
|
onClick = { onPasswordSave }
|
|
|
|
onKeyPress = { onPasswordSaveKeyPressHandler }
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{ t('dialog.add') }</a>
|
2020-04-01 07:47:51 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (locked) {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<a
|
2021-06-10 12:48:44 +00:00
|
|
|
aria-label = { t('dialog.Remove') }
|
2020-04-01 07:47:51 +00:00
|
|
|
className = 'remove-password'
|
2021-06-10 12:48:44 +00:00
|
|
|
onClick = { onPasswordRemove }
|
|
|
|
onKeyPress = { onPasswordRemoveKeyPressHandler }
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{ t('dialog.Remove') }</a>
|
2020-11-05 17:43:48 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
// There are cases like lobby and grant moderator when password is not available
|
|
|
|
password ? <>
|
|
|
|
<a
|
2021-06-10 12:48:44 +00:00
|
|
|
aria-label = { t('dialog.copy') }
|
2020-11-05 17:43:48 +00:00
|
|
|
className = 'copy-password'
|
2021-06-10 12:48:44 +00:00
|
|
|
onClick = { onPasswordCopy }
|
|
|
|
onKeyPress = { onPasswordCopyKeyPressHandler }
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{ t('dialog.copy') }</a>
|
2020-11-05 17:43:48 +00:00
|
|
|
</> : null
|
|
|
|
}
|
2022-10-04 10:52:37 +00:00
|
|
|
{locked === LOCKED_LOCALLY && (
|
|
|
|
<a
|
|
|
|
aria-label = { t(passwordVisible ? 'dialog.hide' : 'dialog.show') }
|
|
|
|
onClick = { passwordVisible ? onPasswordHide : onPasswordShow }
|
|
|
|
onKeyPress = { passwordVisible
|
|
|
|
? onPasswordHideKeyPressHandler
|
|
|
|
: onPasswordShowKeyPressHandler
|
|
|
|
}
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{t(passwordVisible ? 'dialog.hide' : 'dialog.show')}</a>
|
|
|
|
)}
|
2020-04-01 07:47:51 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<a
|
2021-06-10 12:48:44 +00:00
|
|
|
aria-label = { t('info.addPassword') }
|
2020-04-01 07:47:51 +00:00
|
|
|
className = 'add-password'
|
2021-06-10 12:48:44 +00:00
|
|
|
onClick = { onTogglePasswordEditState }
|
|
|
|
onKeyPress = { onTogglePasswordEditStateKeyPressHandler }
|
|
|
|
role = 'button'
|
|
|
|
tabIndex = { 0 }>{ t('info.addPassword') }</a>
|
2020-04-01 07:47:51 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2020-05-20 08:25:31 +00:00
|
|
|
<div className = 'security-dialog password-section'>
|
2020-06-25 13:44:17 +00:00
|
|
|
<p className = 'description'>
|
2020-06-25 13:59:55 +00:00
|
|
|
{ t(canEditPassword ? 'security.about' : 'security.aboutReadOnly') }
|
2020-06-25 13:44:17 +00:00
|
|
|
</p>
|
2020-05-20 08:25:31 +00:00
|
|
|
<div className = 'security-dialog password'>
|
|
|
|
<div
|
|
|
|
className = 'info-dialog info-dialog-column info-dialog-password'
|
|
|
|
ref = { formRef }>
|
|
|
|
<PasswordForm
|
|
|
|
editEnabled = { passwordEditEnabled }
|
|
|
|
locked = { locked }
|
|
|
|
onSubmit = { onPasswordSubmit }
|
|
|
|
password = { password }
|
2022-10-04 10:52:37 +00:00
|
|
|
passwordNumberOfDigits = { passwordNumberOfDigits }
|
|
|
|
visible = { passwordVisible } />
|
2020-05-20 08:25:31 +00:00
|
|
|
</div>
|
|
|
|
<div className = 'security-dialog password-actions'>
|
|
|
|
{ renderPasswordActions() }
|
|
|
|
</div>
|
2020-04-01 07:47:51 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default translate(PasswordSection);
|