forked from vergnet/application-amicale
Improve requests handlers to match linter
This commit is contained in:
parent
9fc02baf6d
commit
0a9e0eb0ca
2 changed files with 268 additions and 274 deletions
|
@ -1,49 +1,55 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import ConnectionManager from "../../managers/ConnectionManager";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import {ERROR_TYPE} from "../../utils/WebData";
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
import ErrorView from "../Screens/ErrorView";
|
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||||
import BasicLoadingScreen from "../Screens/BasicLoadingScreen";
|
import {ERROR_TYPE} from '../../utils/WebData';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import ErrorView from '../Screens/ErrorView';
|
||||||
|
import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
requests: Array<{
|
requests: Array<{
|
||||||
link: string,
|
link: string,
|
||||||
params: Object,
|
params: {...},
|
||||||
mandatory: boolean
|
mandatory: boolean,
|
||||||
}>,
|
}>,
|
||||||
renderFunction: (Array<{ [key: string]: any } | null>) => React.Node,
|
renderFunction: (Array<ApiGenericDataType | null>) => React.Node,
|
||||||
errorViewOverride?: Array<{
|
errorViewOverride?: Array<{
|
||||||
errorCode: number,
|
errorCode: number,
|
||||||
message: string,
|
message: string,
|
||||||
icon: string,
|
icon: string,
|
||||||
showRetryButton: boolean
|
showRetryButton: boolean,
|
||||||
}>,
|
}> | null,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
}
|
};
|
||||||
|
|
||||||
class AuthenticatedScreen extends React.Component<Props, State> {
|
class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
||||||
|
static defaultProps = {
|
||||||
state = {
|
errorViewOverride: null,
|
||||||
loading: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
currentUserToken: string | null;
|
currentUserToken: string | null;
|
||||||
connectionManager: ConnectionManager;
|
|
||||||
errors: Array<number>;
|
|
||||||
fetchedData: Array<{ [key: string]: any } | null>;
|
|
||||||
|
|
||||||
constructor(props: Object) {
|
connectionManager: ConnectionManager;
|
||||||
|
|
||||||
|
errors: Array<number>;
|
||||||
|
|
||||||
|
fetchedData: Array<ApiGenericDataType | null>;
|
||||||
|
|
||||||
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
this.connectionManager = ConnectionManager.getInstance();
|
this.connectionManager = ConnectionManager.getInstance();
|
||||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
this.fetchedData = new Array(this.props.requests.length);
|
this.fetchedData = new Array(props.requests.length);
|
||||||
this.errors = new Array(this.props.requests.length);
|
this.errors = new Array(props.requests.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,35 +62,6 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the data from the server.
|
|
||||||
*
|
|
||||||
* If the user is not logged in errorCode is set to BAD_TOKEN and all requests fail.
|
|
||||||
*
|
|
||||||
* If the user is logged in, send all requests.
|
|
||||||
*/
|
|
||||||
fetchData = () => {
|
|
||||||
if (!this.state.loading)
|
|
||||||
this.setState({loading: true});
|
|
||||||
if (this.connectionManager.isLoggedIn()) {
|
|
||||||
for (let i = 0; i < this.props.requests.length; i++) {
|
|
||||||
this.connectionManager.authenticatedRequest(
|
|
||||||
this.props.requests[i].link,
|
|
||||||
this.props.requests[i].params)
|
|
||||||
.then((data) => {
|
|
||||||
this.onRequestFinished(data, i, -1);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.onRequestFinished(null, i, error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < this.props.requests.length; i++) {
|
|
||||||
this.onRequestFinished(null, i, ERROR_TYPE.BAD_TOKEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when a request finishes, successfully or not.
|
* Callback used when a request finishes, successfully or not.
|
||||||
* Saves data and error code.
|
* Saves data and error code.
|
||||||
|
@ -95,51 +72,20 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
* @param index The index for the data
|
* @param index The index for the data
|
||||||
* @param error The error code received
|
* @param error The error code received
|
||||||
*/
|
*/
|
||||||
onRequestFinished(data: { [key: string]: any } | null, index: number, error: number) {
|
onRequestFinished(
|
||||||
if (index >= 0 && index < this.props.requests.length) {
|
data: ApiGenericDataType | null,
|
||||||
|
index: number,
|
||||||
|
error?: number,
|
||||||
|
) {
|
||||||
|
const {props} = this;
|
||||||
|
if (index >= 0 && index < props.requests.length) {
|
||||||
this.fetchedData[index] = data;
|
this.fetchedData[index] = data;
|
||||||
this.errors[index] = error;
|
this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
|
||||||
}
|
}
|
||||||
|
// Token expired, logout user
|
||||||
|
if (error === ERROR_TYPE.BAD_TOKEN) this.connectionManager.disconnect();
|
||||||
|
|
||||||
if (error === ERROR_TYPE.BAD_TOKEN) // Token expired, logout user
|
if (this.allRequestsFinished()) this.setState({loading: false});
|
||||||
this.connectionManager.disconnect();
|
|
||||||
|
|
||||||
if (this.allRequestsFinished())
|
|
||||||
this.setState({loading: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if all requests finished processing
|
|
||||||
*
|
|
||||||
* @return {boolean} True if all finished
|
|
||||||
*/
|
|
||||||
allRequestsFinished() {
|
|
||||||
let finished = true;
|
|
||||||
for (let i = 0; i < this.fetchedData.length; i++) {
|
|
||||||
if (this.fetchedData[i] === undefined) {
|
|
||||||
finished = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if all requests have finished successfully.
|
|
||||||
* This will return false only if a mandatory request failed.
|
|
||||||
* All non-mandatory requests can fail without impacting the return value.
|
|
||||||
*
|
|
||||||
* @return {boolean} True if all finished successfully
|
|
||||||
*/
|
|
||||||
allRequestsValid() {
|
|
||||||
let valid = true;
|
|
||||||
for (let i = 0; i < this.fetchedData.length; i++) {
|
|
||||||
if (this.fetchedData[i] === null && this.props.requests[i].mandatory) {
|
|
||||||
valid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return valid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,9 +95,13 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
|
* @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
|
||||||
*/
|
*/
|
||||||
getError() {
|
getError(): number {
|
||||||
for (let i = 0; i < this.errors.length; i++) {
|
const {props} = this;
|
||||||
if (this.errors[i] !== 0 && this.props.requests[i].mandatory) {
|
for (let i = 0; i < this.errors.length; i += 1) {
|
||||||
|
if (
|
||||||
|
this.errors[i] !== ERROR_TYPE.SUCCESS &&
|
||||||
|
props.requests[i].mandatory
|
||||||
|
) {
|
||||||
return this.errors[i];
|
return this.errors[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,13 +113,14 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getErrorRender() {
|
getErrorRender(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
const errorCode = this.getError();
|
const errorCode = this.getError();
|
||||||
let shouldOverride = false;
|
let shouldOverride = false;
|
||||||
let override = null;
|
let override = null;
|
||||||
const overrideList = this.props.errorViewOverride;
|
const overrideList = props.errorViewOverride;
|
||||||
if (overrideList != null) {
|
if (overrideList != null) {
|
||||||
for (let i = 0; i < overrideList.length; i++) {
|
for (let i = 0; i < overrideList.length; i += 1) {
|
||||||
if (overrideList[i].errorCode === errorCode) {
|
if (overrideList[i].errorCode === errorCode) {
|
||||||
shouldOverride = true;
|
shouldOverride = true;
|
||||||
override = overrideList[i];
|
override = overrideList[i];
|
||||||
|
@ -177,25 +128,62 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldOverride && override != null) {
|
if (shouldOverride && override != null) {
|
||||||
return (
|
return (
|
||||||
<ErrorView
|
<ErrorView
|
||||||
{...this.props}
|
|
||||||
icon={override.icon}
|
icon={override.icon}
|
||||||
message={override.message}
|
message={override.message}
|
||||||
showRetryButton={override.showRetryButton}
|
showRetryButton={override.showRetryButton}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
return (
|
return <ErrorView errorCode={errorCode} onRefresh={this.fetchData} />;
|
||||||
<ErrorView
|
|
||||||
{...this.props}
|
|
||||||
errorCode={errorCode}
|
|
||||||
onRefresh={this.fetchData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the data from the server.
|
||||||
|
*
|
||||||
|
* If the user is not logged in errorCode is set to BAD_TOKEN and all requests fail.
|
||||||
|
*
|
||||||
|
* If the user is logged in, send all requests.
|
||||||
|
*/
|
||||||
|
fetchData = () => {
|
||||||
|
const {state, props} = this;
|
||||||
|
if (!state.loading) this.setState({loading: true});
|
||||||
|
|
||||||
|
if (this.connectionManager.isLoggedIn()) {
|
||||||
|
for (let i = 0; i < props.requests.length; i += 1) {
|
||||||
|
this.connectionManager
|
||||||
|
.authenticatedRequest(
|
||||||
|
props.requests[i].link,
|
||||||
|
props.requests[i].params,
|
||||||
|
)
|
||||||
|
.then((response: ApiGenericDataType): void =>
|
||||||
|
this.onRequestFinished(response, i),
|
||||||
|
)
|
||||||
|
.catch((error: number): void =>
|
||||||
|
this.onRequestFinished(null, i, error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < props.requests.length; i += 1) {
|
||||||
|
this.onRequestFinished(null, i, ERROR_TYPE.BAD_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if all requests finished processing
|
||||||
|
*
|
||||||
|
* @return {boolean} True if all finished
|
||||||
|
*/
|
||||||
|
allRequestsFinished(): boolean {
|
||||||
|
let finished = true;
|
||||||
|
this.errors.forEach((error: number | null) => {
|
||||||
|
if (error == null) finished = false;
|
||||||
|
});
|
||||||
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,14 +193,12 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
return (
|
const {state, props} = this;
|
||||||
this.state.loading
|
if (state.loading) return <BasicLoadingScreen />;
|
||||||
? <BasicLoadingScreen/>
|
if (this.getError() === ERROR_TYPE.SUCCESS)
|
||||||
: (this.allRequestsValid()
|
return props.renderFunction(this.fetchedData);
|
||||||
? this.props.renderFunction(this.fetchedData)
|
return this.getErrorRender();
|
||||||
: this.getErrorRender())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,37 @@ export const ERROR_TYPE = {
|
||||||
UNKNOWN: 999,
|
UNKNOWN: 999,
|
||||||
};
|
};
|
||||||
|
|
||||||
type response_format = {
|
export type ApiDataLoginType = {
|
||||||
error: number,
|
token: string,
|
||||||
data: Object,
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const API_ENDPOINT = "https://www.amicale-insat.fr/api/";
|
// eslint-disable-next-line flowtype/no-weak-types
|
||||||
|
export type ApiGenericDataType = {[key: string]: any};
|
||||||
|
|
||||||
|
type ApiResponseType = {
|
||||||
|
error: number,
|
||||||
|
data: ApiGenericDataType,
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_ENDPOINT = 'https://www.amicale-insat.fr/api/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given API response is valid.
|
||||||
|
*
|
||||||
|
* For a request to be valid, it must match the response_format as defined in this file.
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isApiResponseValid(response: ApiResponseType): boolean {
|
||||||
|
return (
|
||||||
|
response != null &&
|
||||||
|
response.error != null &&
|
||||||
|
typeof response.error === 'number' &&
|
||||||
|
response.data != null &&
|
||||||
|
typeof response.data === 'object'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a request to the Amicale Website backend
|
* Sends a request to the Amicale Website backend
|
||||||
|
@ -30,55 +55,40 @@ const API_ENDPOINT = "https://www.amicale-insat.fr/api/";
|
||||||
* @param path The API path from the API endpoint
|
* @param path The API path from the API endpoint
|
||||||
* @param method The HTTP method to use (GET or POST)
|
* @param method The HTTP method to use (GET or POST)
|
||||||
* @param params The params to use for this request
|
* @param params The params to use for this request
|
||||||
* @returns {Promise<R>}
|
* @returns {Promise<ApiGenericDataType>}
|
||||||
*/
|
*/
|
||||||
export async function apiRequest(path: string, method: string, params: ?{ [key: string]: string | number }) {
|
export async function apiRequest(
|
||||||
if (params === undefined || params === null)
|
path: string,
|
||||||
params = {};
|
method: string,
|
||||||
|
params?: {...},
|
||||||
return new Promise((resolve, reject) => {
|
): Promise<ApiGenericDataType> {
|
||||||
|
return new Promise(
|
||||||
|
(
|
||||||
|
resolve: (data: ApiGenericDataType) => void,
|
||||||
|
reject: (error: number) => void,
|
||||||
|
) => {
|
||||||
|
let requestParams = {};
|
||||||
|
if (params != null) requestParams = {...params};
|
||||||
fetch(API_ENDPOINT + path, {
|
fetch(API_ENDPOINT + path, {
|
||||||
method: method,
|
method,
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Accept': 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestParams),
|
||||||
...params
|
|
||||||
})
|
})
|
||||||
}).then(async (response) => response.json())
|
.then(async (response: Response): Promise<ApiResponseType> =>
|
||||||
.then((response: response_format) => {
|
response.json(),
|
||||||
if (isResponseValid(response)) {
|
)
|
||||||
if (response.error === ERROR_TYPE.SUCCESS)
|
.then((response: ApiResponseType) => {
|
||||||
resolve(response.data);
|
if (isApiResponseValid(response)) {
|
||||||
else
|
if (response.error === ERROR_TYPE.SUCCESS) resolve(response.data);
|
||||||
reject(response.error);
|
else reject(response.error);
|
||||||
} else
|
} else reject(ERROR_TYPE.SERVER_ERROR);
|
||||||
reject(ERROR_TYPE.SERVER_ERROR);
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((): void => reject(ERROR_TYPE.CONNECTION_ERROR));
|
||||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
},
|
||||||
});
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given API response is valid.
|
|
||||||
*
|
|
||||||
* For a request to be valid, it must match the response_format as defined in this file.
|
|
||||||
*
|
|
||||||
* @param response
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function isResponseValid(response: response_format) {
|
|
||||||
let valid = response !== undefined
|
|
||||||
&& response.error !== undefined
|
|
||||||
&& typeof response.error === "number";
|
|
||||||
|
|
||||||
valid = valid
|
|
||||||
&& response.data !== undefined
|
|
||||||
&& typeof response.data === "object";
|
|
||||||
return valid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,17 +100,15 @@ export function isResponseValid(response: response_format) {
|
||||||
* If no data was found, returns an empty object
|
* If no data was found, returns an empty object
|
||||||
*
|
*
|
||||||
* @param url The urls to fetch data from
|
* @param url The urls to fetch data from
|
||||||
* @return {Promise<Object>}
|
* @return Promise<{...}>
|
||||||
*/
|
*/
|
||||||
export async function readData(url: string) {
|
export async function readData(url: string): Promise<{...}> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(
|
||||||
|
(resolve: (response: {...}) => void, reject: () => void) => {
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(async (response) => response.json())
|
.then(async (response: Response): Promise<{...}> => response.json())
|
||||||
.then((data) => {
|
.then((data: {...}): void => resolve(data))
|
||||||
resolve(data);
|
.catch((): void => reject());
|
||||||
})
|
},
|
||||||
.catch(() => {
|
);
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue