diff --git a/src/Screens/Main/Tabs.tsx b/src/Screens/Main/Tabs.tsx index 8491af85..98603115 100644 --- a/src/Screens/Main/Tabs.tsx +++ b/src/Screens/Main/Tabs.tsx @@ -1,13 +1,18 @@ import React from 'react'; import * as ReactRedux from 'react-redux'; +import * as redux from 'redux'; import { selectFirstQuestion } from '../../state/reducers/questions'; +import { selectClientVersion } from '../../state/reducers/minimumVersion'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import * as actions from '../../state/actions'; + import { StackScreenProps } from '@react-navigation/stack'; import { CommonActions } from '@react-navigation/native'; import { StackRoutes } from '..'; -import { useRefetch } from 'src/lib/use-refetch'; +import { useRefetch } from '../../lib/use-refetch'; +import { getClient } from '../../lib/isDevice'; import BuddyList, { BuddyListRoute } from './BuddyList'; import MentorList, { MentorListRoute } from './MentorList'; @@ -30,10 +35,13 @@ const Tab = createBottomTabNavigator(); type Props = {} & StackScreenProps; const Main = ({ navigation, route }: Props) => { - const dispatch = ReactRedux.useDispatch(); - + const dispatch = ReactRedux.useDispatch>(); const initialRouteName = route.params?.initial; + const clientVersion = ReactRedux.useSelector( + selectClientVersion(getClient()), + ); + const handleRefetchData = () => { dispatch({ type: 'feedback/getQuestions/start', payload: undefined }); dispatch({ type: 'mentors/start', payload: undefined }); @@ -65,6 +73,12 @@ const Main = ({ navigation, route }: Props) => { }); }, []); + React.useEffect(() => { + dispatch({ type: 'minimumVersion/get/start', payload: undefined }); + }, []); + + console.log('got minimum client version from API', clientVersion); + return ( <> ; - -const VersionCheck = ({ navigation }: Props) => { - const dispatch = useDispatch>(); - React.useEffect(() => { - dispatch({ type: 'minimumVersion/get/start', payload: undefined }); - }, []); - - const versionState = useSelector(selectClientVersions); - - React.useEffect(() => { - if (RD.isSuccess(versionState)) { - navigation.replace('Splash', {}); - } - }, [versionState]); - - return ; -}; - -const styles = RN.StyleSheet.create({ - background: { - backgroundColor: colors.purplePale, - flex: 1, - }, -}); - -export default VersionCheck; diff --git a/src/Screens/index.tsx b/src/Screens/index.tsx index b623aa25..ac3acd25 100644 --- a/src/Screens/index.tsx +++ b/src/Screens/index.tsx @@ -28,10 +28,8 @@ import DeleteAccount, { import PasswordChange, { PasswordChangeRoute } from './Main/Settings/Password'; import EmailChange, { EmailChangeRoute } from './Main/Settings/Email/Email'; import UserReport, { UserReportRoute } from './Main/UserReport'; -import { VersionCheckRoute } from './VersionCheck'; export type StackRoutes = SplashRoute & - VersionCheckRoute & WelcomeRoute & MentorListRoute & SignRoute & @@ -56,7 +54,7 @@ const Stack = reactNavigationStack.createStackNavigator(); export default () => ( diff --git a/src/api/minimumVersion.ts b/src/api/minimumVersion.ts index 43997f9b..672d7914 100644 --- a/src/api/minimumVersion.ts +++ b/src/api/minimumVersion.ts @@ -4,21 +4,47 @@ import * as TE from 'fp-ts/lib/TaskEither'; import * as http from '../lib/http'; import * as config from './config'; +import * as authApi from './auth'; +import { ValidVersion } from '../lib/validators'; -const clientVersions = t.strict({ - ylitse_ios: t.string, - ylitse_android: t.string, +const client = t.strict({ + client: t.string, + version: ValidVersion, }); +type Client = t.TypeOf; + +const clientVersions = t.array(client); + const versionsResponse = t.strict({ resources: clientVersions, }); -export type ClientVersions = t.TypeOf; +export type Version = { + client: string; + major: number; + minor: number; + patch: number; +}; + +const toAppVersion = (value: Client): Version => { + const splitted = value.version.split('.').map(Number); + + return { + client: value.client, + major: splitted[0], + minor: splitted[1], + patch: splitted[2], + }; +}; -export const fetchVersions: () => TE.TaskEither = () => +export const fetchVersions = ( + token: authApi.AccessToken, +): TE.TaskEither> => http.validateResponse( - http.get(`${config.baseUrl}/version/clients`), + http.get(`${config.baseUrl}/version/clients`, { + headers: authApi.authHeader(token), + }), versionsResponse, - response => response.resources, + response => response.resources.map(toAppVersion), ); diff --git a/src/lib/__tests__/validators.ts b/src/lib/__tests__/validators.ts index 10326c0d..93bd0be8 100644 --- a/src/lib/__tests__/validators.ts +++ b/src/lib/__tests__/validators.ts @@ -108,15 +108,51 @@ describe('Validate The valid password to be true', () => { }); }); -describe('Validate The invalid password to be true', () => { +describe('Validate The invalid password', () => { [ '', 'password22', 'PasswordSalasana', 'ÄteritsiputeRitsipuolilautatsijänk', ].forEach(password => { - it(`decodes valid password ${password}`, () => { + it(`decodes inValid password ${password}`, () => { expect(isLeft(validators.ValidPassword.decode(password))).toEqual(true); }); }); }); + +describe('Validate correct versions', () => { + ['1.23.3', '10.20.0', '3.99.99', '2.11.1'].forEach(version => { + it(`decodes valid version ${version}`, () => { + expect(isRight(validators.ValidVersion.decode(version))).toEqual(true); + }); + }); +}); + +describe('Validate invalid versions', () => { + [ + '1.2', + '1.2.3.4', + '123.45.6', + '1.234.5', + '01.2.3', + '1.2.03', + 'a.b.c', + '1.2.a', + '', + '1..2', + '.1.2.3', + '1.2.3.', + '1.2.', + '1..', + '1', + null, + undefined, + 123, + {}, + ].forEach(version => { + it(`invalidates invalid version ${version}`, () => { + expect(isLeft(validators.ValidVersion.decode(version))).toEqual(true); + }); + }); +}); diff --git a/src/lib/http.ts b/src/lib/http.ts index 69766c5d..77cf992f 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -64,6 +64,11 @@ const decode = TE.fromEither, ); +// const log = (value: A) => { +// console.log(value); +// return value; +// }; + export const validateResponse = ( task: TE.TaskEither, model: t.Type, diff --git a/src/lib/isDevice.ts b/src/lib/isDevice.ts index df7f15a1..d45f3916 100644 --- a/src/lib/isDevice.ts +++ b/src/lib/isDevice.ts @@ -6,3 +6,11 @@ export const isDevice = (platform: PlatformOSType) => platform === RN.Platform.OS; export const hasNotch = () => Device.hasNotch(); + +export const getClient = () => { + if (RN.Platform.OS === 'android') { + return 'ylitse_android'; + } + + return 'ylitse_ios'; +}; diff --git a/src/lib/validators.ts b/src/lib/validators.ts index 303543ce..3f51bac9 100644 --- a/src/lib/validators.ts +++ b/src/lib/validators.ts @@ -52,6 +52,10 @@ export const isValidDescription = (value: string): boolean => { return /^(?!\s*$).+/.test(value); }; +const isValidVersion = (value: string): boolean => { + return /^\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(value); +}; + export const ValidEmail = new t.Type( 'ValidEmail', (input: unknown): input is string => typeof input === 'string', @@ -91,3 +95,19 @@ export const ValidPassword = new t.Type( : t.failure(input, context), t.identity, ); + +export const ValidVersion = new t.Type( + 'ValidVersion', + (input: unknown): input is string => typeof input === 'string', + (input, context) => + either.chain(t.string.validate(input, context), str => { + const noLeadingZeros = str + .split('.') + .every(part => part === '0' || !part.startsWith('0')); + + return isValidVersion(str) && noLeadingZeros + ? t.success(str) + : t.failure(input, context); + }), + t.identity, +); diff --git a/src/state/reducers/minimumVersion.ts b/src/state/reducers/minimumVersion.ts index 5a52cc38..7562181e 100644 --- a/src/state/reducers/minimumVersion.ts +++ b/src/state/reducers/minimumVersion.ts @@ -2,13 +2,12 @@ import { Reducer } from 'redux-automaton'; import * as RD from '@devexperts/remote-data-ts'; import * as automaton from 'redux-automaton'; import * as actions from '../actions'; -import * as T from 'fp-ts/lib/Task'; import * as minimumVersionApi from '../../api/minimumVersion'; -import { cmd } from '../middleware'; import { Action } from '../actions'; import { AppState } from '../types'; +import { withToken } from './accessToken'; import { pipe } from 'fp-ts/lib/function'; @@ -26,11 +25,9 @@ export const reducer: Reducer = ( return automaton.loop( RD.pending, - cmd( - pipe( - minimumVersionApi.fetchVersions(), - T.map(actions.make('minimumVersion/get/end')), - ), + withToken( + minimumVersionApi.fetchVersions, + actions.make('minimumVersion/get/end'), ), ); } @@ -45,5 +42,16 @@ export const reducer: Reducer = ( } }; -export const selectClientVersions = ({ minimumVersion }: AppState) => - minimumVersion; +export const selectClientVersion = + (client: string) => + ({ minimumVersion }: AppState) => { + return pipe( + minimumVersion, + RD.fold( + () => undefined, // NotAsked + () => undefined, // Loading + () => undefined, // Failure + clients => clients.find(p => p.client === client), + ), + ); + }; diff --git a/src/state/types.ts b/src/state/types.ts index b60b1aa4..69b0cd12 100644 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -76,5 +76,5 @@ export type AppState = { sendDeviceToken: RemoteAction; }; feedbackQuestions: RemoteData>; - minimumVersion: RemoteData; + minimumVersion: RemoteData>; };