Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

SecurityDialog.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. import React, { PureComponent } from 'react';
  2. import {
  3. Text,
  4. View
  5. } from 'react-native';
  6. import { connect } from 'react-redux';
  7. import type { Dispatch } from 'redux';
  8. import { getSecurityUiConfig } from '../../../../base/config/functions.any';
  9. import { MEETING_PASSWORD_ENABLED } from '../../../../base/flags/constants';
  10. import { getFeatureFlag } from '../../../../base/flags/functions';
  11. import { translate } from '../../../../base/i18n/functions';
  12. import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
  13. import { isLocalParticipantModerator } from '../../../../base/participants/functions';
  14. import Button from '../../../../base/ui/components/native/Button';
  15. import Input from '../../../../base/ui/components/native/Input';
  16. import Switch from '../../../../base/ui/components/native/Switch';
  17. import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
  18. import { copyText } from '../../../../base/util/copyText.native';
  19. import { isInBreakoutRoom } from '../../../../breakout-rooms/functions';
  20. import { toggleLobbyMode } from '../../../../lobby/actions.any';
  21. import {
  22. endRoomLockRequest,
  23. unlockRoom
  24. } from '../../../../room-lock/actions';
  25. import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../../../room-lock/constants';
  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. <Switch
  162. checked = { _lobbyEnabled }
  163. onChange = { 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(_isModerator ? 'security.about' : 'security.aboutReadOnly') }
  274. </Text>
  275. <View
  276. style = {
  277. _locked !== LOCKED_REMOTELY
  278. && styles.passwordContainerControls
  279. }>
  280. <View>
  281. { this._setRoomPasswordMessage() }
  282. </View>
  283. { _isModerator && 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.customContainer }}
  322. onChange = { this._onChangeText }
  323. placeholder = { t('dialog.password') }
  324. value = { passwordInputValue }
  325. { ...textInputProps } />
  326. );
  327. } else if (_locked) {
  328. if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
  329. return (
  330. <View style = { styles.savedPasswordContainer }>
  331. <Text style = { styles.savedPasswordLabel }>
  332. { t('info.password') }
  333. </Text>
  334. <Text style = { styles.savedPassword }>
  335. { _password }
  336. </Text>
  337. </View>
  338. );
  339. }
  340. }
  341. }
  342. }
  343. _onToggleLobbyMode: () => void;
  344. /**
  345. * Handles the enable-disable lobby mode switch.
  346. *
  347. * @private
  348. * @returns {void}
  349. */
  350. _onToggleLobbyMode() {
  351. const { _lobbyEnabled, dispatch } = this.props;
  352. if (_lobbyEnabled) {
  353. dispatch(toggleLobbyMode(false));
  354. } else {
  355. dispatch(toggleLobbyMode(true));
  356. }
  357. }
  358. _onAddPassword: () => void;
  359. /**
  360. * Callback to be invoked when add password button is pressed.
  361. *
  362. * @returns {void}
  363. */
  364. _onAddPassword() {
  365. const { showElement } = this.state;
  366. this.setState({
  367. showElement: !showElement
  368. });
  369. }
  370. /**
  371. * Verifies input in case only digits are required.
  372. *
  373. * @param {string} passwordInputValue - The value of the password
  374. * text input.
  375. * @private
  376. * @returns {boolean} False when the value is not valid and True otherwise.
  377. */
  378. _validateInputValue(passwordInputValue: string) {
  379. const { _passwordNumberOfDigits } = this.props;
  380. // we want only digits,
  381. // but both number-pad and numeric add ',' and '.' as symbols
  382. if (_passwordNumberOfDigits
  383. && passwordInputValue.length > 0
  384. && !/^\d+$/.test(passwordInputValue)) {
  385. return false;
  386. }
  387. return true;
  388. }
  389. _onChangeText: string => void;
  390. /**
  391. * Callback to be invoked when the text in the field changes.
  392. *
  393. * @param {string} passwordInputValue - The value of password input.
  394. * @returns {void}
  395. */
  396. _onChangeText(passwordInputValue) {
  397. if (!this._validateInputValue(passwordInputValue)) {
  398. return;
  399. }
  400. this.setState({
  401. passwordInputValue
  402. });
  403. }
  404. _onCancel: () => void;
  405. /**
  406. * Cancels value typed in text input.
  407. *
  408. * @returns {void}
  409. */
  410. _onCancel() {
  411. this.setState({
  412. passwordInputValue: '',
  413. showElement: false
  414. });
  415. this.props.dispatch(unlockRoom());
  416. }
  417. _onCopy: () => void;
  418. /**
  419. * Copies room password.
  420. *
  421. * @returns {void}
  422. */
  423. _onCopy() {
  424. const { passwordInputValue } = this.state;
  425. copyText(passwordInputValue);
  426. }
  427. _onSubmit: () => void;
  428. /**
  429. * Submits value typed in text input.
  430. *
  431. * @returns {void}
  432. */
  433. _onSubmit() {
  434. const {
  435. _conference,
  436. dispatch
  437. } = this.props;
  438. const { passwordInputValue } = this.state;
  439. dispatch(endRoomLockRequest(_conference, passwordInputValue));
  440. }
  441. }
  442. /**
  443. * Maps part of the Redux state to the props of this component.
  444. *
  445. * @param {Object} state - The Redux state.
  446. * @returns {Props}
  447. */
  448. function _mapStateToProps(state: Object): Object {
  449. const { conference, locked, password } = state['features/base/conference'];
  450. const { disableLobbyPassword, hideLobbyButton } = getSecurityUiConfig(state);
  451. const { lobbyEnabled } = state['features/lobby'];
  452. const { roomPasswordNumberOfDigits } = state['features/base/config'];
  453. const lobbySupported = conference && conference.isLobbySupported();
  454. const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
  455. return {
  456. _conference: conference,
  457. _isModerator: isLocalParticipantModerator(state),
  458. _lobbyEnabled: lobbyEnabled,
  459. _lobbyModeSwitchVisible:
  460. lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton && !isInBreakoutRoom(state),
  461. _locked: locked,
  462. _lockedConference: Boolean(conference && locked),
  463. _password: password,
  464. _passwordNumberOfDigits: roomPasswordNumberOfDigits,
  465. _roomPasswordControls: visible && !disableLobbyPassword
  466. };
  467. }
  468. export default translate(connect(_mapStateToProps)(SecurityDialog));