Recover credentials on startup for increased performance and added login/profile button on home

This commit is contained in:
Arnaud Vergnet 2020-04-04 16:09:04 +02:00
parent 1ede8f4e9a
commit 17016b6452
6 changed files with 85 additions and 62 deletions

5
App.js
View file

@ -14,6 +14,7 @@ import {initExpoToken} from "./utils/Notifications";
import {Provider as PaperProvider} from 'react-native-paper'; import {Provider as PaperProvider} from 'react-native-paper';
import AprilFoolsManager from "./managers/AprilFoolsManager"; import AprilFoolsManager from "./managers/AprilFoolsManager";
import Update from "./constants/Update"; import Update from "./constants/Update";
import ConnectionManager from "./managers/ConnectionManager";
type Props = {}; type Props = {};
@ -91,6 +92,10 @@ export default class App extends React.Component<Props, State> {
await AsyncStorageManager.getInstance().loadPreferences(); await AsyncStorageManager.getInstance().loadPreferences();
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme); ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
await initExpoToken(); await initExpoToken();
try {
await ConnectionManager.getInstance().recoverLogin();
} catch (e) {}
this.onLoadFinished(); this.onLoadFinished();
} }

View file

@ -24,7 +24,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
loading: true, loading: true,
}; };
currentUserToken: string; currentUserToken: string | null;
connectionManager: ConnectionManager; connectionManager: ConnectionManager;
errorCode: number; errorCode: number;
data: Object; data: Object;
@ -35,8 +35,6 @@ class AuthenticatedScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
this.connectionManager = ConnectionManager.getInstance(); this.connectionManager = ConnectionManager.getInstance();
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this)); this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
this.fetchData();
} }
onScreenFocus() { onScreenFocus() {
@ -47,26 +45,24 @@ class AuthenticatedScreen extends React.Component<Props, State> {
fetchData = () => { fetchData = () => {
if (!this.state.loading) if (!this.state.loading)
this.setState({loading: true}); this.setState({loading: true});
this.connectionManager.isLoggedIn() if (this.connectionManager.isLoggedIn()) {
.then(() => { this.connectionManager.authenticatedRequest(this.props.link)
this.connectionManager.authenticatedRequest(this.props.link) .then((data) => {
.then((data) => { this.onFinishedLoading(data, -1);
this.onFinishedLoading(data, -1); })
}) .catch((error) => {
.catch((error) => { this.onFinishedLoading(undefined, error);
this.onFinishedLoading(undefined, error); });
}); } else {
}) this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
.catch((error) => { }
this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
});
}; };
onFinishedLoading(data: Object, error: number) { onFinishedLoading(data: Object, error: number) {
this.data = data; this.data = data;
this.currentUserToken = data !== undefined this.currentUserToken = data !== undefined
? this.connectionManager.getToken() ? this.connectionManager.getToken()
: ''; : null;
this.errorCode = error; this.errorCode = error;
this.setState({loading: false}); this.setState({loading: false});
} }

View file

@ -154,13 +154,12 @@ class SideBar extends React.PureComponent<Props, State> {
]; ];
this.getRenderItem = this.getRenderItem.bind(this); this.getRenderItem = this.getRenderItem.bind(this);
this.colors = props.theme.colors; this.colors = props.theme.colors;
ConnectionManager.getInstance().setLoginCallback((value) => this.onLoginStateChange(value)); ConnectionManager.getInstance().addLoginStateListener((value) => this.onLoginStateChange(value));
this.state = { this.state = {
active: 'Home', active: 'Home',
isLoggedIn: false, isLoggedIn: ConnectionManager.getInstance().isLoggedIn(),
dialogVisible: false, dialogVisible: false,
}; };
ConnectionManager.getInstance().isLoggedIn().then(data => undefined).catch(error => undefined);
} }
showDisconnectDialog = () => this.setState({ dialogVisible: true }); showDisconnectDialog = () => this.setState({ dialogVisible: true });

View file

@ -16,10 +16,17 @@ export default class ConnectionManager {
static instance: ConnectionManager | null = null; static instance: ConnectionManager | null = null;
#email: string; #email: string;
#token: string; #token: string | null;
loginCallback: Function; loginCallback: Function;
listeners: Array<Function>;
constructor() {
this.#token = null;
this.listeners = [];
}
/** /**
* Get this class instance or create one if none is found * Get this class instance or create one if none is found
* @returns {ConnectionManager} * @returns {ConnectionManager}
@ -35,20 +42,24 @@ export default class ConnectionManager {
} }
onLoginStateChange(newState: boolean) { onLoginStateChange(newState: boolean) {
this.loginCallback(newState); for (let i = 0; i < this.listeners.length; i++) {
if (this.listeners[i] !== undefined)
this.listeners[i](newState);
}
} }
setLoginCallback(callback: Function) { addLoginStateListener(listener: Function) {
this.loginCallback = callback; this.listeners.push(listener);
} }
async recoverLogin() { async recoverLogin() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.#token !== undefined) if (this.#token !== null)
resolve(this.#token); resolve(this.#token);
else { else {
SecureStore.getItemAsync('token') SecureStore.getItemAsync('token')
.then((token) => { .then((token) => {
this.#token = token;
if (token !== null) { if (token !== null) {
this.onLoginStateChange(true); this.onLoginStateChange(true);
resolve(token); resolve(token);
@ -62,16 +73,8 @@ export default class ConnectionManager {
}); });
} }
async isLoggedIn() { isLoggedIn() {
return new Promise((resolve, reject) => { return this.#token !== null;
this.recoverLogin()
.then(() => {
resolve(true);
})
.catch(() => {
reject(false);
})
});
} }
async saveLogin(email: string, token: string) { async saveLogin(email: string, token: string) {
@ -93,6 +96,7 @@ export default class ConnectionManager {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
SecureStore.deleteItemAsync('token') SecureStore.deleteItemAsync('token')
.then(() => { .then(() => {
this.#token = null;
this.onLoginStateChange(false); this.onLoginStateChange(false);
resolve(true); resolve(true);
}) })
@ -166,32 +170,29 @@ export default class ConnectionManager {
async authenticatedRequest(url: string) { async authenticatedRequest(url: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.recoverLogin() if (this.#token !== null) {
.then(token => { fetch(url, {
fetch(url, { method: 'POST',
method: 'POST', headers: new Headers({
headers: new Headers({ 'Accept': 'application/json',
'Accept': 'application/json', 'Content-Type': 'application/json',
'Content-Type': 'application/json', }),
}), body: JSON.stringify({token: this.#token})
body: JSON.stringify({token: token}) }).then(async (response) => response.json())
}).then(async (response) => response.json()) .then((data) => {
.then((data) => { if (this.isRequestResponseValid(data)) {
if (this.isRequestResponseValid(data)) { if (data.state)
if (data.state) resolve(data.data);
resolve(data.data); else
else reject(ERROR_TYPE.BAD_CREDENTIALS);
reject(ERROR_TYPE.BAD_CREDENTIALS); } else
} else
reject(ERROR_TYPE.CONNECTION_ERROR);
})
.catch(() => {
reject(ERROR_TYPE.CONNECTION_ERROR); reject(ERROR_TYPE.CONNECTION_ERROR);
}); })
}) .catch(() => {
.catch(() => { reject(ERROR_TYPE.CONNECTION_ERROR);
reject(ERROR_TYPE.NO_TOKEN); });
}); } else
reject(ERROR_TYPE.NO_TOKEN);
}); });
} }
} }

View file

@ -32,8 +32,8 @@ const emailRegex = /^.+@.+\..+$/;
class LoginScreen extends React.Component<Props, State> { class LoginScreen extends React.Component<Props, State> {
state = { state = {
email: '', email: 'vergnet@etud.insa-toulouse.fr',
password: '', password: '3D514ùdsqg',
isEmailValidated: false, isEmailValidated: false,
isPasswordValidated: false, isPasswordValidated: false,
loading: false, loading: false,

View file

@ -12,6 +12,8 @@ import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardI
import {stringToDate} from "../utils/Planning"; import {stringToDate} from "../utils/Planning";
import {openBrowser} from "../utils/WebBrowser"; import {openBrowser} from "../utils/WebBrowser";
import ActionsDashBoardItem from "../components/Home/ActionsDashboardItem"; import ActionsDashBoardItem from "../components/Home/ActionsDashboardItem";
import HeaderButton from "../components/Custom/HeaderButton";
import ConnectionManager from "../managers/ConnectionManager";
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -33,6 +35,7 @@ type Props = {
type State = { type State = {
imageModalVisible: boolean, imageModalVisible: boolean,
imageList: Array<Object>, imageList: Array<Object>,
isLoggedIn: boolean,
} }
/** /**
@ -52,10 +55,12 @@ class HomeScreen extends React.Component<Props, State> {
state = { state = {
imageModalVisible: false, imageModalVisible: false,
imageList: [], imageList: [],
isLoggedIn: ConnectionManager.getInstance().isLoggedIn(),
}; };
constructor(props) { constructor(props) {
super(props); super(props);
ConnectionManager.getInstance().addLoginStateListener((value) => this.setState({isLoggedIn: value}));
this.onProxiwashClick = this.onProxiwashClick.bind(this); this.onProxiwashClick = this.onProxiwashClick.bind(this);
this.onTutorInsaClick = this.onTutorInsaClick.bind(this); this.onTutorInsaClick = this.onTutorInsaClick.bind(this);
this.onMenuClick = this.onMenuClick.bind(this); this.onMenuClick = this.onMenuClick.bind(this);
@ -76,6 +81,23 @@ class HomeScreen extends React.Component<Props, State> {
return date.toLocaleString(); return date.toLocaleString();
} }
componentDidMount() {
this.props.navigation.setOptions({
headerRight: this.getHeaderButton,
});
}
getHeaderButton = () => {
const screen = this.state.isLoggedIn
? "ProfileScreen"
: "LoginScreen";
const icon = this.state.isLoggedIn
? "account"
: "login";
const onPress = () => this.props.navigation.navigate(screen);
return <HeaderButton icon={icon} onPress={onPress}/>;
};
onProxiwashClick() { onProxiwashClick() {
this.props.navigation.navigate('Proxiwash'); this.props.navigation.navigate('Proxiwash');
} }