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 15KB

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