diff --git a/App.js b/App.js new file mode 100644 index 0000000..7a2af99 --- /dev/null +++ b/App.js @@ -0,0 +1,16 @@ +import AuthLoading from './src/AuthLoading' +import Messages from './src/Messages' +import Login from './src/Login' +import { createSwitchNavigator } from 'react-navigation'; + +export default createSwitchNavigator({ + Messages: { + screen: Messages, + }, + Login: { + screen: Login, + }, + AuthLoading: { + screen: AuthLoading + }, +}, {initialRouteName: 'AuthLoading'}); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..01e6538 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +import { AppRegistry, YellowBox } from 'react-native'; +import App from './App'; + +YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated', 'Module RCTImageLoader']); + +AppRegistry.registerComponent('gotify', () => App); diff --git a/src/AuthLoading.js b/src/AuthLoading.js new file mode 100644 index 0000000..b73bae8 --- /dev/null +++ b/src/AuthLoading.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { + ActivityIndicator, + AsyncStorage, + StatusBar, + StyleSheet, + View, +} from 'react-native'; +import SharedPreferences from 'react-native-shared-preferences'; + +export default class AuthLoadingScreen extends React.Component { + componentDidMount() { + SharedPreferences.getItem('@global:token', (token) => { + this.props.navigation.navigate(token ? 'Messages' : 'Login'); + }); + } + + render() { + return ( + + + + + ); + } +} \ No newline at end of file diff --git a/src/Login.js b/src/Login.js new file mode 100644 index 0000000..c810241 --- /dev/null +++ b/src/Login.js @@ -0,0 +1,154 @@ +import React from 'react'; +import { View, Text, TextInput, Button, ToastAndroid } from 'react-native'; +import SharedPreferences from 'react-native-shared-preferences'; +import Icon from 'react-native-vector-icons/MaterialIcons' +import DeviceInfo from 'react-native-device-info'; +import * as axios from "axios"; + +const urlRegex = new RegExp("^(http|https)://", "i"); +const defaultClientName = DeviceInfo.getManufacturer() + ' ' + DeviceInfo.getDeviceId(); +export default class Login extends React.Component { + state = {tryConnect: null, url: null, error: null, version: null, name: '', pass: '', loggedIn: null, client: defaultClientName}; + componentDidMount() { + SharedPreferences.getItem("@global:url", (url) => { + if (url) { + this.setState({...this.state, url: url}, this.checkUrl); + } + }); + } + + handleChange = (name, merge) => { + return (val) => { + const old = {...this.state, ...(merge || {})}; + old[name] = val; + this.setState(old); + } + }; + + reachedButNot = () => { + this.setState({...this.state, tryConnect: false, error: 'server exists, but it is not a valid gotify instance'}) + }; + + notReachable = () => { + this.setState({...this.state, tryConnect: false, error: 'could not reach ' + this.state.url}) + }; + + createClient = () => { + const {client, url, pass, name} = this.state; + axios.post(url + 'client', {name: client}, {auth:{username: name, password: pass}}).then((resp) => { + SharedPreferences.setItem('@global:token', resp.data.token); + ToastAndroid.show("Created client " + client + " for user " + name, ToastAndroid.SHORT); + this.props.navigation.navigate('AuthLoading') + }).catch(() => { + ToastAndroid.show("Could not create client", ToastAndroid.SHORT); + }) + }; + + version(url, errorCallback, reachedButNoApi) { + axios.get(url + 'version').then((resp) => { + if (resp && resp.status === 200) { + const {data} = resp; + if (data + && typeof data === 'object' + && 'version' in data + && 'buildDate' in data + && 'commit' in data + ) { + SharedPreferences.setItem('@global:url', url); + this.setState({...this.state, url: url, tryConnect: true, error: null, version: data}); + return; + } + } + (reachedButNoApi ? reachedButNoApi : errorCallback)(); + }).catch(errorCallback); + } + + checkUrl = () => { + let {url} = this.state; + if (!url.endsWith('/')) { + url += '/'; + } + + if (urlRegex.test(url)) { + if(url.match(/^http:\/\//i)){ + this.version(url.replace(/^http:\/\//i,"https://"), () => { + this.version(url, this.notReachable, this.reachedButNot) + }); + } else { + this.version(url, this.notReachable,this.reachedButNot) + } + } else { + this.setState({...this.state, error: 'url must either start with http:// or https://'}) + } + }; + + checkUser = () => { + const {url, pass, name} = this.state; + axios.get(url + 'current/user', {auth:{username: name, password: pass}}).then((resp) => { + this.setState({...this.state, loggedIn: true}) + }).catch((resp) => { + this.setState({...this.state, loggedIn: false}) + }) + }; + + render() { + const {tryConnect, error, version, url, client, loggedIn} = this.state; + + return ( + + + Gotify + + + + + {error && {error}} + {version && Gotify v{version.version}} + {tryConnect === true && ( + + + + + + + + {loggedIn === true && ( + + + + valid user + + + +