You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SecurityDialog.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. import Clipboard from '@react-native-community/clipboard';
  2. import React, { PureComponent } from 'react';
  3. import {
  4. Text,
  5. View
  6. } from 'react-native';
  7. import type { Dispatch } from 'redux';
  8. import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags';
  9. import { translate } from '../../../../base/i18n';
  10. import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
  11. import { isLocalParticipantModerator } from '../../../../base/participants';
  12. import { connect } from '../../../../base/redux';
  13. import BaseTheme from '../../../../base/ui/components/BaseTheme';
  14. import Button from '../../../../base/ui/components/native/Button';
  15. import Input from '../../../../base/ui/components/native/Input';
  16. import { BUTTON_TYPES } from '../../../../base/ui/constants';
  17. import { isInBreakoutRoom } from '../../../../breakout-rooms/functions';
  18. import { toggleLobbyMode } from '../../../../lobby/actions.any';
  19. import LobbyModeSwitch
  20. from '../../../../lobby/components/native/LobbyModeSwitch';
  21. import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../../../room-lock';
  22. import {
  23. endRoomLockRequest,
  24. unlockRoom
  25. } from '../../../../room-lock/actions';
  26. import styles from './styles';
  27. /**
  28. * The style of the {@link TextInput} rendered by {@code SecurityDialog}. As it
  29. * requests the entry of a password, {@code TextInput} automatically correcting
  30. * the entry of the password is a pain to deal with as a user.
  31. */
  32. const _TEXT_INPUT_PROPS = {
  33. autoCapitalize: 'none',
  34. autoCorrect: false
  35. };
  36. /**
  37. * The type of the React {@code Component} props of {@link SecurityDialog}.
  38. */
  39. type Props = {
  40. /**
  41. * The JitsiConference which requires a password.
  42. */
  43. _conference: Object,
  44. /**
  45. * Whether the local user is the moderator.
  46. */
  47. _isModerator: boolean,
  48. /**
  49. * State of the lobby mode.
  50. */
  51. _lobbyEnabled: boolean,
  52. /**
  53. * Whether the lobby mode switch is available or not.
  54. */
  55. _lobbyModeSwitchVisible: boolean,
  56. /**
  57. * The value for how the conference is locked (or undefined if not locked)
  58. * as defined by room-lock constants.
  59. */
  60. _locked: string,
  61. /**
  62. * Checks if the conference room is locked or not.
  63. */
  64. _lockedConference: boolean,
  65. /**
  66. * The current known password for the JitsiConference.
  67. */
  68. _password: string,
  69. /**
  70. * Number of digits used in the room-lock password.
  71. */
  72. _passwordNumberOfDigits: number,
  73. /**
  74. * Whether setting a room password is available or not.
  75. */
  76. _roomPasswordControls: boolean,
  77. /**
  78. * Redux store dispatch function.
  79. */
  80. dispatch: Dispatch<any>,
  81. /**
  82. * Invoked to obtain translated strings.
  83. */
  84. t: Function
  85. };
  86. /**
  87. * The type of the React {@code Component} state of {@link SecurityDialog}.
  88. */
  89. type State = {
  90. /**
  91. * Password added by the participant for room lock.
  92. */
  93. passwordInputValue: string,
  94. /**
  95. * Shows an input or a message.
  96. */
  97. showElement: boolean
  98. };
  99. /**
  100. * Component that renders the security options dialog.
  101. *
  102. * @returns {React$Element<any>}
  103. */
  104. class SecurityDialog extends PureComponent<Props, State> {
  105. /**
  106. * Instantiates a new {@code SecurityDialog}.
  107. *
  108. * @inheritdoc
  109. */
  110. constructor(props: Props) {
  111. super(props);
  112. this.state = {
  113. passwordInputValue: '',
  114. showElement: props._locked === LOCKED_LOCALLY || false
  115. };
  116. this._onChangeText = this._onChangeText.bind(this);
  117. this._onCancel = this._onCancel.bind(this);
  118. this._onCopy = this._onCopy.bind(this);
  119. this._onSubmit = this._onSubmit.bind(this);
  120. this._onToggleLobbyMode = this._onToggleLobbyMode.bind(this);
  121. this._onAddPassword = this._onAddPassword.bind(this);
  122. }
  123. /**
  124. * Implements {@code SecurityDialog.render}.
  125. *
  126. * @inheritdoc
  127. */
  128. render() {
  129. return (
  130. <JitsiScreen style = { styles.securityDialogContainer }>
  131. { this._renderLobbyMode() }
  132. { this._renderSetRoomPassword() }
  133. </JitsiScreen>
  134. );
  135. }
  136. /**
  137. * Renders lobby mode.
  138. *
  139. * @returns {ReactElement}
  140. * @private
  141. */
  142. _renderLobbyMode() {
  143. const {
  144. _lobbyEnabled,
  145. _lobbyModeSwitchVisible,
  146. t
  147. } = this.props;
  148. if (!_lobbyModeSwitchVisible) {
  149. return null;
  150. }
  151. return (
  152. <View style = { styles.lobbyModeContainer }>
  153. <View style = { styles.lobbyModeContent } >
  154. <Text style = { styles.lobbyModeText }>
  155. { t('lobby.enableDialogText') }
  156. </Text>
  157. <View style = { styles.lobbyModeSection }>
  158. <Text style = { styles.lobbyModeLabel } >
  159. { t('lobby.toggleLabel') }
  160. </Text>
  161. <LobbyModeSwitch
  162. lobbyEnabled = { _lobbyEnabled }
  163. onToggleLobbyMode = { this._onToggleLobbyMode } />
  164. </View>
  165. </View>
  166. </View>
  167. );
  168. }
  169. /**
  170. * Renders setting the password.
  171. *
  172. * @returns {ReactElement}
  173. * @private
  174. */
  175. _renderSetRoomPassword() {
  176. const {
  177. _isModerator,
  178. _locked,
  179. _lockedConference,
  180. _password,
  181. _roomPasswordControls,
  182. t
  183. } = this.props;
  184. const { showElement } = this.state;
  185. let setPasswordControls;
  186. if (!_roomPasswordControls) {
  187. return null;
  188. }
  189. if (_locked && showElement) {
  190. setPasswordControls = (
  191. <>
  192. <Button
  193. accessibilityLabel = 'dialog.Remove'
  194. labelKey = 'dialog.Remove'
  195. labelStyle = { styles.passwordSetupButtonLabel }
  196. onClick = { this._onCancel }
  197. type = { BUTTON_TYPES.TERTIARY } />
  198. {
  199. _password
  200. && <Button
  201. accessibilityLabel = 'dialog.copy'
  202. labelKey = 'dialog.copy'
  203. labelStyle = { styles.passwordSetupButtonLabel }
  204. onClick = { this._onCopy }
  205. type = { BUTTON_TYPES.TERTIARY } />
  206. }
  207. </>
  208. );
  209. } else if (!_lockedConference && showElement) {
  210. setPasswordControls = (
  211. <>
  212. <Button
  213. accessibilityLabel = 'dialog.Cancel'
  214. labelKey = 'dialog.Cancel'
  215. labelStyle = { styles.passwordSetupButtonLabel }
  216. onClick = { this._onCancel }
  217. type = { BUTTON_TYPES.TERTIARY } />
  218. <Button
  219. accessibilityLabel = 'dialog.add'
  220. labelKey = 'dialog.add'
  221. labelStyle = { styles.passwordSetupButtonLabel }
  222. onClick = { this._onSubmit }
  223. type = { BUTTON_TYPES.TERTIARY } />
  224. </>
  225. );
  226. } else if (!_lockedConference && !showElement) {
  227. setPasswordControls = (
  228. <Button
  229. accessibilityLabel = 'info.addPassword'
  230. disabled = { !_isModerator }
  231. labelKey = 'info.addPassword'
  232. labelStyle = { styles.passwordSetupButtonLabel }
  233. onClick = { this._onAddPassword }
  234. type = { BUTTON_TYPES.TERTIARY } />
  235. );
  236. }
  237. if (_locked === LOCKED_REMOTELY) {
  238. if (_isModerator) {
  239. setPasswordControls = (
  240. <View style = { styles.passwordSetRemotelyContainer }>
  241. <Text style = { styles.passwordSetRemotelyText }>
  242. { t('passwordSetRemotely') }
  243. </Text>
  244. <Button
  245. accessibilityLabel = 'dialog.Remove'
  246. labelKey = 'dialog.Remove'
  247. labelStyle = { styles.passwordSetupButtonLabel }
  248. onClick = { this._onCancel }
  249. type = { BUTTON_TYPES.TERTIARY } />
  250. </View>
  251. );
  252. } else {
  253. setPasswordControls = (
  254. <View style = { styles.passwordSetRemotelyContainer }>
  255. <Text style = { styles.passwordSetRemotelyTextDisabled }>
  256. { t('passwordSetRemotely') }
  257. </Text>
  258. <Button
  259. accessibilityLabel = 'info.addPassword'
  260. disabled = { !_isModerator }
  261. labelKey = 'info.addPassword'
  262. labelStyle = { styles.passwordSetupButtonLabel }
  263. onClick = { this._onAddPassword }
  264. type = { BUTTON_TYPES.TERTIARY } />
  265. </View>
  266. );
  267. }
  268. }
  269. return (
  270. <View
  271. style = { styles.passwordContainer } >
  272. <Text style = { styles.passwordContainerText }>
  273. { t('security.about') }
  274. </Text>
  275. <View
  276. style = {
  277. _locked !== LOCKED_REMOTELY
  278. && styles.passwordContainerControls
  279. }>
  280. <View>
  281. { this._setRoomPasswordMessage() }
  282. </View>
  283. { setPasswordControls }
  284. </View>
  285. </View>
  286. );
  287. }
  288. /**
  289. * Renders room lock text input/message.
  290. *
  291. * @returns {ReactElement}
  292. * @private
  293. */
  294. _setRoomPasswordMessage() {
  295. let textInputProps = _TEXT_INPUT_PROPS;
  296. const {
  297. _isModerator,
  298. _locked,
  299. _password,
  300. _passwordNumberOfDigits,
  301. t
  302. } = this.props;
  303. const { passwordInputValue, showElement } = this.state;
  304. if (_passwordNumberOfDigits) {
  305. textInputProps = {
  306. ...textInputProps,
  307. keyboardType: 'numeric',
  308. maxLength: _passwordNumberOfDigits
  309. };
  310. }
  311. if (!_isModerator) {
  312. return null;
  313. }
  314. if (showElement) {
  315. if (typeof _locked === 'undefined') {
  316. return (
  317. <Input
  318. accessibilityLabel = { t('info.addPassword') }
  319. autoFocus = { true }
  320. clearable = { true }
  321. customStyles = {{ container: styles.passwordInput }}
  322. onChange = { this._onChangeText }
  323. placeholder = { t('dialog.password') }
  324. placeholderTextColor = { BaseTheme.palette.text03 }
  325. value = { passwordInputValue }
  326. { ...textInputProps } />
  327. );
  328. } else if (_locked) {
  329. if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
  330. return (
  331. <View style = { styles.savedPasswordContainer }>
  332. <Text style = { styles.savedPasswordLabel }>
  333. { t('info.password') }
  334. </Text>
  335. <Text style = { styles.savedPassword }>
  336. { passwordInputValue }
  337. </Text>
  338. </View>
  339. );
  340. }
  341. }
  342. }
  343. }
  344. _onToggleLobbyMode: () => void;
  345. /**
  346. * Handles the enable-disable lobby mode switch.
  347. *
  348. * @private
  349. * @returns {void}
  350. */
  351. _onToggleLobbyMode() {
  352. const { _lobbyEnabled, dispatch } = this.props;
  353. if (_lobbyEnabled) {
  354. dispatch(toggleLobbyMode(false));
  355. } else {
  356. dispatch(toggleLobbyMode(true));
  357. }
  358. }
  359. _onAddPassword: () => void;
  360. /**
  361. * Callback to be invoked when add password button is pressed.
  362. *
  363. * @returns {void}
  364. */
  365. _onAddPassword() {
  366. const { showElement } = this.state;
  367. this.setState({
  368. showElement: !showElement
  369. });
  370. }
  371. /**
  372. * Verifies input in case only digits are required.
  373. *
  374. * @param {string} passwordInputValue - The value of the password
  375. * text input.
  376. * @private
  377. * @returns {boolean} False when the value is not valid and True otherwise.
  378. */
  379. _validateInputValue(passwordInputValue: string) {
  380. const { _passwordNumberOfDigits } = this.props;
  381. // we want only digits,
  382. // but both number-pad and numeric add ',' and '.' as symbols
  383. if (_passwordNumberOfDigits
  384. && passwordInputValue.length > 0
  385. && !/^\d+$/.test(passwordInputValue)) {
  386. return false;
  387. }
  388. return true;
  389. }
  390. _onChangeText: string => void;
  391. /**
  392. * Callback to be invoked when the text in the field changes.
  393. *
  394. * @param {string} passwordInputValue - The value of password input.
  395. * @returns {void}
  396. */
  397. _onChangeText(passwordInputValue) {
  398. if (!this._validateInputValue(passwordInputValue)) {
  399. return;
  400. }
  401. this.setState({
  402. passwordInputValue
  403. });
  404. }
  405. _onCancel: () => void;
  406. /**
  407. * Cancels value typed in text input.
  408. *
  409. * @returns {void}
  410. */
  411. _onCancel() {
  412. this.setState({
  413. passwordInputValue: '',
  414. showElement: false
  415. });
  416. this.props.dispatch(unlockRoom());
  417. }
  418. _onCopy: () => void;
  419. /**
  420. * Copies room password.
  421. *
  422. * @returns {void}
  423. */
  424. _onCopy() {
  425. const { passwordInputValue } = this.state;
  426. Clipboard.setString(passwordInputValue);
  427. }
  428. _onSubmit: () => void;
  429. /**
  430. * Submits value typed in text input.
  431. *
  432. * @returns {void}
  433. */
  434. _onSubmit() {
  435. const {
  436. _conference,
  437. dispatch
  438. } = this.props;
  439. const { passwordInputValue } = this.state;
  440. dispatch(endRoomLockRequest(_conference, passwordInputValue));
  441. }
  442. }
  443. /**
  444. * Maps part of the Redux state to the props of this component.
  445. *
  446. * @param {Object} state - The Redux state.
  447. * @returns {Props}
  448. */
  449. function _mapStateToProps(state: Object): Object {
  450. const { conference, locked, password } = state['features/base/conference'];
  451. const { hideLobbyButton } = state['features/base/config'];
  452. const { lobbyEnabled } = state['features/lobby'];
  453. const { roomPasswordNumberOfDigits } = state['features/base/config'];
  454. const lobbySupported = conference && conference.isLobbySupported();
  455. const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
  456. return {
  457. _conference: conference,
  458. _isModerator: isLocalParticipantModerator(state),
  459. _lobbyEnabled: lobbyEnabled,
  460. _lobbyModeSwitchVisible:
  461. lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton && !isInBreakoutRoom(state),
  462. _locked: locked,
  463. _lockedConference: Boolean(conference && locked),
  464. _password: password,
  465. _passwordNumberOfDigits: roomPasswordNumberOfDigits,
  466. _roomPasswordControls: visible
  467. };
  468. }
  469. export default translate(connect(_mapStateToProps)(SecurityDialog));