From 0b702dc4cada6fa279598da5f9b2aad9e93a1cc9 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 11 Aug 2017 12:47:18 -0300 Subject: [PATCH] Validating URL whena adding new server. Closes #17 --- app/views/serverNew.js | 169 ++++++++++++++++++++++++++++++++++++++--- package.json | 5 +- 2 files changed, 160 insertions(+), 14 deletions(-) diff --git a/app/views/serverNew.js b/app/views/serverNew.js index 3a933e8f..7c859c3a 100644 --- a/app/views/serverNew.js +++ b/app/views/serverNew.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { TextInput, StyleSheet } from 'react-native'; +import { Text, TextInput, View, StyleSheet } from 'react-native'; +import _ from 'underscore'; import RocketChat from '../lib/rocketchat'; @@ -10,7 +11,6 @@ const styles = StyleSheet.create({ view: { flex: 1, flexDirection: 'column', - justifyContent: 'center', alignItems: 'stretch' }, input: { @@ -24,6 +24,26 @@ const styles = StyleSheet.create({ text: { textAlign: 'center', color: '#888' + }, + validateText: { + position: 'absolute', + color: 'green', + textAlign: 'center', + paddingLeft: 50, + paddingRight: 50, + width: '100%' + }, + validText: { + color: 'green' + }, + invalidText: { + color: 'red' + }, + validatingText: { + color: '#aaa' + }, + spaceView: { + flexGrow: 1 } }); @@ -40,6 +60,7 @@ export default class NewServerView extends React.Component { super(props); this.state = { defaultServer: 'https://demo.rocket.chat', + editable: true, text: '' }; @@ -47,36 +68,160 @@ export default class NewServerView extends React.Component { let url = this.state.text.trim(); if (!url) { url = this.state.defaultServer; + } else { + url = this.completeUrl(this.state.text); } - // TODO: validate URL - if (url.indexOf('.') === -1) { - url = `https://${ url }.rocket.chat`; - } + this.setState({ + editable: false + }); - if (/^https?:\/\//.test(url) === false) { + this.inputElement.blur(); + this.validateServer(url).then(() => { + RocketChat.currentServer = url; + this.props.navigation.dispatch({ type: 'Navigation/BACK' }); + }).catch(() => { + this.setState({ + editable: true + }); + this.inputElement.focus(); + }); + }; + } + + componentDidMount() { + this._mounted = true; + } + + componentWillUnmount() { + this._mounted = false; + } + + onChangeText = (text) => { + this.setState({ text }); + + this.validateServerDebounced(text); + } + + validateServer = url => new Promise((resolve, reject) => { + url = this.completeUrl(url); + + this.setState({ + validating: false, + url + }); + + if (/^(https?:\/\/)?(((\w|[0-9])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) { + this.setState({ + validating: true + }); + + const a = fetch(url, { method: 'HEAD' }) + .then((response) => { + if (!this._mounted) { + return; + } + if (response.status === 200 && response.headers.get('x-instance-id') != null && response.headers.get('x-instance-id').length) { + this.setState({ + validInstance: true, + validating: false + }); + resolve(url); + } else { + this.setState({ + validInstance: false, + validating: false + }); + reject(url); + } + }) + .catch(() => { + if (!this._mounted) { + return; + } + this.setState({ + validInstance: false, + validating: false + }); + reject(url); + }); + console.log(a.stop); + } else { + this.setState({ + validInstance: undefined + }); + reject(url); + } + }) + + validateServerDebounced = _.debounce(this.validateServer, 1000) + + completeUrl = (url) => { + url = url.trim(); + + if (/^(\w|[0-9-_]){3,}$/.test(url) && /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) { + url = `${ url }.rocket.chat`; + } + + if (/^(https?:\/\/)?(((\w|[0-9])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) { + if (/^localhost(:\d+)?/.test(url)) { + url = `http://${ url }`; + } else if (/^https?:\/\//.test(url) === false) { url = `https://${ url }`; } + } - RocketChat.currentServer = url; - this.props.navigation.dispatch({ type: 'Navigation/BACK' }); - }; + url = url.replace(/\/+$/, ''); + + return url; + } + + renderValidation = () => { + if (this.state.validating) { + return ( + + Validating {this.state.url} ... + + ); + } + + if (this.state.validInstance) { + return ( + + {this.state.url} is a valid Rocket.Chat instance + + ); + } + + if (this.state.validInstance === false) { + return ( + + {this.state.url} is not a valid Rocket.Chat instance + + ); + } } render() { return ( - + + this.inputElement = ref} style={styles.input} - onChangeText={text => this.setState({ text })} + onChangeText={this.onChangeText} keyboardType='url' autoCorrect={false} returnKeyType='done' autoCapitalize='none' autoFocus + editable={this.state.editable} onSubmitEditing={this.submit} placeholder={this.state.defaultServer} /> + + {this.renderValidation()} + ); } diff --git a/package.json b/package.json index 36c730b1..5cc619e6 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "react-native-action-button": "^2.7.2", "react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git", "react-native-fetch-blob": "^0.10.8", - "react-native-image-picker": "^0.26.3", "react-native-form-generator": "^0.9.9", + "react-native-image-picker": "^0.26.3", "react-native-img-cache": "^1.4.0", "react-native-meteor": "^1.1.0", "react-native-svg": "^5.4.1", @@ -28,7 +28,8 @@ "react-native-zeroconf": "^0.8.1", "react-navigation": "^1.0.0-beta.11", "realm": "^1.10.1", - "strip-ansi": "^4.0.0" + "strip-ansi": "^4.0.0", + "underscore": "^1.8.3" }, "devDependencies": { "babel-eslint": "^7.2.3",