[RN] Validate the URL in app-settings
This commit is contained in:
parent
c4468cb7b8
commit
6a9e6db3be
|
@ -539,6 +539,9 @@
|
|||
"tooltip": "Get access info about the meeting"
|
||||
},
|
||||
"profileModal": {
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Warning",
|
||||
"alertURLText": "The entered server URL is invalid",
|
||||
"conferenceSection": "Conference",
|
||||
"displayName": "Display name",
|
||||
"email": "Email",
|
||||
|
|
|
@ -4,8 +4,6 @@ import { Component } from 'react';
|
|||
|
||||
import { getProfile, updateProfile } from '../../base/profile';
|
||||
|
||||
import { hideAppSettings } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AbstractAppSettings}
|
||||
*/
|
||||
|
@ -34,7 +32,12 @@ type Props = {
|
|||
/**
|
||||
* Redux store dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<*>
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* The i18n translate function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -44,6 +47,7 @@ type Props = {
|
|||
* @abstract
|
||||
*/
|
||||
export class AbstractAppSettings extends Component<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractAppSettings} instance.
|
||||
*
|
||||
|
@ -56,7 +60,6 @@ export class AbstractAppSettings extends Component<Props> {
|
|||
this._onChangeDisplayName = this._onChangeDisplayName.bind(this);
|
||||
this._onChangeEmail = this._onChangeEmail.bind(this);
|
||||
this._onChangeServerURL = this._onChangeServerURL.bind(this);
|
||||
this._onRequestClose = this._onRequestClose.bind(this);
|
||||
this._onStartAudioMutedChange
|
||||
= this._onStartAudioMutedChange.bind(this);
|
||||
this._onStartVideoMutedChange
|
||||
|
@ -108,17 +111,6 @@ export class AbstractAppSettings extends Component<Props> {
|
|||
});
|
||||
}
|
||||
|
||||
_onRequestClose: () => void;
|
||||
|
||||
/**
|
||||
* Handles the back button.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onRequestClose() {
|
||||
this.props.dispatch(hideAppSettings());
|
||||
}
|
||||
|
||||
_onStartAudioMutedChange: (boolean) => void;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Modal,
|
||||
ScrollView,
|
||||
Switch,
|
||||
|
@ -11,13 +14,14 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { ASPECT_RATIO_NARROW } from '../../base/aspect-ratio';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { isIPad } from '../../base/react';
|
||||
import { getSafetyOffset, isIPad } from '../../base/react';
|
||||
|
||||
import { _mapStateToProps, AbstractAppSettings } from './AbstractAppSettings';
|
||||
import BackButton from './BackButton';
|
||||
import FormRow from './FormRow';
|
||||
import FormSectionHeader from './FormSectionHeader';
|
||||
import { getSafetyOffset } from '../functions';
|
||||
import { hideAppSettings } from '../actions';
|
||||
import BackButton from './BackButton.native';
|
||||
import FormRow from './FormRow.native';
|
||||
import FormSectionHeader from './FormSectionHeader.native';
|
||||
import { normalizeUserInputURL } from '../functions';
|
||||
import styles, { HEADER_PADDING } from './styles';
|
||||
|
||||
/**
|
||||
|
@ -26,6 +30,8 @@ import styles, { HEADER_PADDING } from './styles';
|
|||
* @extends AbstractAppSettings
|
||||
*/
|
||||
class AppSettings extends AbstractAppSettings {
|
||||
_urlField: Object;
|
||||
|
||||
/**
|
||||
* Instantiates a new {@code AppSettings} instance.
|
||||
*
|
||||
|
@ -35,6 +41,10 @@ class AppSettings extends AbstractAppSettings {
|
|||
super(props);
|
||||
|
||||
this._getSafetyPadding = this._getSafetyPadding.bind(this);
|
||||
this._onBlurServerURL = this._onBlurServerURL.bind(this);
|
||||
this._onRequestClose = this._onRequestClose.bind(this);
|
||||
this._setURLFieldReference = this._setURLFieldReference.bind(this);
|
||||
this._showURLAlert = this._showURLAlert.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,8 +107,10 @@ class AppSettings extends AbstractAppSettings {
|
|||
i18nLabel = 'profileModal.serverURL' >
|
||||
<TextInput
|
||||
autoCapitalize = 'none'
|
||||
onBlur = { this._onBlurServerURL }
|
||||
onChangeText = { this._onChangeServerURL }
|
||||
placeholder = { this.props._serverURL }
|
||||
ref = { this._setURLFieldReference }
|
||||
value = { _profile.serverURL } />
|
||||
</FormRow>
|
||||
<FormRow
|
||||
|
@ -127,6 +139,8 @@ class AppSettings extends AbstractAppSettings {
|
|||
);
|
||||
}
|
||||
|
||||
_getSafetyPadding: () => Object;
|
||||
|
||||
/**
|
||||
* Calculates header safety padding for mobile devices. See comment in
|
||||
* functions.js.
|
||||
|
@ -145,6 +159,100 @@ class AppSettings extends AbstractAppSettings {
|
|||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_onBlurServerURL: () => void;
|
||||
|
||||
/**
|
||||
* Handler the server URL lose focus event. Here we validate the server URL
|
||||
* and update it to the normalized version, or show an error if incorrect.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onBlurServerURL() {
|
||||
this._processServerURL(false /* hideOnSuccess */);
|
||||
}
|
||||
|
||||
_onChangeDisplayName: (string) => void;
|
||||
|
||||
_onChangeEmail: (string) => void;
|
||||
|
||||
_onChangeServerURL: (string) => void;
|
||||
|
||||
_onStartAudioMutedChange: (boolean) => void;
|
||||
|
||||
_onStartVideoMutedChange: (boolean) => void;
|
||||
|
||||
_onRequestClose: () => void;
|
||||
|
||||
/**
|
||||
* Processes the server URL. It normalizes it and an error alert is
|
||||
* displayed in case it's incorrect.
|
||||
*
|
||||
* @param {boolean} hideOnSuccess - True if the dialog should be hidden if
|
||||
* normalization / validation succeeds, false otherwise.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_processServerURL(hideOnSuccess: boolean) {
|
||||
const { serverURL } = this.props._profile;
|
||||
const normalizedURL = normalizeUserInputURL(serverURL);
|
||||
|
||||
if (normalizedURL === null) {
|
||||
this._showURLAlert();
|
||||
} else {
|
||||
this._onChangeServerURL(normalizedURL);
|
||||
if (hideOnSuccess) {
|
||||
this.props.dispatch(hideAppSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the back button.
|
||||
* Also invokes normalizeUserInputURL to validate the URL entered
|
||||
* by the user.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onRequestClose() {
|
||||
this._processServerURL(true /* hideOnSuccess */);
|
||||
}
|
||||
|
||||
_setURLFieldReference: (React$ElementRef<*> | null) => void;
|
||||
|
||||
/**
|
||||
* Stores a reference to the URL field for later use.
|
||||
*
|
||||
* @protected
|
||||
* @param {Object} component - The field component.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setURLFieldReference(component) {
|
||||
this._urlField = component;
|
||||
}
|
||||
|
||||
_showURLAlert: () => void;
|
||||
|
||||
/**
|
||||
* Shows an alert telling the user that the URL he/she entered was invalid.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_showURLAlert() {
|
||||
const { t } = this.props;
|
||||
|
||||
Alert.alert(
|
||||
t('profileModal.alertTitle'),
|
||||
t('profileModal.alertURLText'),
|
||||
[
|
||||
{
|
||||
onPress: () => this._urlField.focus(),
|
||||
text: t('profileModal.alertOk')
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(AppSettings));
|
||||
|
|
|
@ -6,8 +6,8 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { getSafetyOffset } from '../../base/react';
|
||||
|
||||
import { getSafetyOffset } from '../functions';
|
||||
import styles, { ANDROID_UNDERLINE_COLOR, CONTAINER_PADDING } from './styles';
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,8 +6,8 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { getSafetyOffset } from '../../base/react';
|
||||
|
||||
import { getSafetyOffset } from '../functions';
|
||||
import styles, { CONTAINER_PADDING } from './styles';
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
|
||||
import { parseStandardURIString } from '../base/util';
|
||||
|
||||
/**
|
||||
* Normalizes a URL entered by the user.
|
||||
* FIXME: Consider adding this to base/util/uri.
|
||||
*
|
||||
* @param {string} url - The URL to validate.
|
||||
* @returns {string|null} - The normalized URL, or null if the URL is invalid.
|
||||
*/
|
||||
export function normalizeUserInputURL(url: string) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
if (url) {
|
||||
url = url.replace(/\s/g, '').toLowerCase();
|
||||
const urlRegExp = new RegExp('^(\\w+://)?(.+)$');
|
||||
const urlComponents = urlRegExp.exec(url);
|
||||
|
||||
if (!urlComponents[1] || !urlComponents[1].startsWith('http')) {
|
||||
url = `https://${urlComponents[2]}`;
|
||||
}
|
||||
|
||||
const parsedURI
|
||||
= parseStandardURIString(url);
|
||||
|
||||
if (!parsedURI.host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsedURI.toString();
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { isIPhoneX, Platform } from '../base/react';
|
||||
|
||||
const IPHONE_OFFSET = 20;
|
||||
const IPHONEX_OFFSET = 44;
|
||||
|
||||
/**
|
||||
* Determines the offset to be used for the device. This uses a custom
|
||||
* implementation to minimize empty area around screen, especially on iPhone X.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getSafetyOffset() {
|
||||
if (Platform.OS === 'android') {
|
||||
// Android doesn't need offset, except the Essential phone. Should be
|
||||
// addressed later with a generic solution.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return isIPhoneX() ? IPHONEX_OFFSET : IPHONE_OFFSET;
|
||||
}
|
|
@ -6,6 +6,24 @@ import Platform from './Platform';
|
|||
|
||||
const IPHONEX_HEIGHT = 812;
|
||||
const IPHONEX_WIDTH = 375;
|
||||
const IPHONE_OFFSET = 20;
|
||||
const IPHONEX_OFFSET = 44;
|
||||
|
||||
/**
|
||||
* Determines the offset to be used for the device. This uses a custom
|
||||
* implementation to minimize empty area around screen, especially on iPhone X.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getSafetyOffset() {
|
||||
if (Platform.OS === 'android') {
|
||||
// Android doesn't need offset, except the Essential phone. Should be
|
||||
// addressed later with a generic solution.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return isIPhoneX() ? IPHONEX_OFFSET : IPHONE_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the device is an iPad or not.
|
||||
|
|
Loading…
Reference in New Issue