react-native upgrade to 0.71.1

alpha
Jan Umbach 2023-01-23 16:15:50 +01:00
parent 8b7f244812
commit 15f779a13b
32 changed files with 7191 additions and 1898 deletions

View File

@ -1,4 +1,5 @@
apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
import com.android.build.OutputFile
import org.apache.tools.ant.taskdefs.condition.Os
@ -316,4 +317,4 @@ project.ext.vectoricons = [
iconFontNames: [ 'MaterialIcons.ttf', 'MaterialCommunityIcons.ttf', 'FontAwesome.ttf', 'Ionicons.ttf' ] // Name of the font files you want to copy
]
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

View File

@ -2,18 +2,13 @@
buildscript {
ext {
buildToolsVersion = "31.0.0"
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 31
targetSdkVersion = 31
compileSdkVersion = 33
targetSdkVersion = 33
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
}
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
repositories {
google()

1
mock.js Normal file
View File

@ -0,0 +1 @@
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

6016
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,13 +23,14 @@
"babel-preset-es2015": "^6.24.1",
"babel-preset-esnext": "^1.1.3",
"babel-preset-react": "^6.24.1",
"idb": "^7.1.1",
"native-base": "^3.4.23",
"password-quality-calculator": "^1.0.4",
"react": "^18.1.0",
"react-dom": "18.1.0",
"react-native": "0.70.6",
"react-native": "0.71.1",
"react-native-encrypted-storage": "^4.0.3",
"react-native-gesture-handler": "^2.8.0",
"react-native-reanimated": "^2.13.0",
"react-native-gesture-handler": "^2.8.0",
"react-native-safe-area-context": "^4.4.1",
"react-native-screens": "^3.18.2",
"react-native-svg": "^13.6.0",
@ -37,7 +38,8 @@
"react-native-vector-icons": "^9.2.0",
"react-native-web": "^0.18.10",
"react-redux": "^8.0.5",
"react-string-replace": "^1.1.0"
"react-string-replace": "^1.1.0",
"realm": "^11.3.2"
},
"devDependencies": {
"@babel/core": "^7.12.9",
@ -97,7 +99,7 @@
"<rootDir>"
],
"setupFiles": [
"./tests/setup.js"
"./mock.js"
]
},
"browser": [

View File

@ -15,15 +15,25 @@ import {useEffect} from 'react';
import {initAppData} from '@caj/helper/appData';
import {appStatus} from '@caj/configs/appNonSaveVar';
import {appNonSaveVarActions} from '@caj/configs/appNonSaveVarReducer';
import BigDataManager from '@caj/helper/storage/BigDataManager';
const AnimationView = animated(View);
function onAppStart() {
initAppData().then(() => {
console.log('finish');
setTimeout(() => {
store.dispatch(appNonSaveVarActions.setAppStatus(appStatus.APP_RUNNING));
}, 250);
BigDataManager.initDatabase()
.then(() => {
console.log('finish');
setTimeout(() => {
store.dispatch(
appNonSaveVarActions.setAppStatus(appStatus.APP_RUNNING),
);
}, 250);
})
.catch(err => {
console.error("Database Error! Can't start App :(");
});
//store.dispatch(actions.loadPreferences(appVar));
});
}

View File

@ -1,147 +0,0 @@
import React, {useState, useEffect} from 'react';
import {StyleSheet, Appearance} from 'react-native';
import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context';
import {useSelector, useDispatch} from 'react-redux';
import {RootState} from '@caj/redux/store';
import {appVarActions} from '@caj/configs/appVarReducer';
import imgSrc from '@caj/img/maimg.png';
import {placeholder} from '@caj/lang/default';
import {getBackgroundColor} from '@caj/configs/colors';
import {saveVarChanges} from '@caj/helper/appData';
import {Box, Input, VStack, Center, Avatar, Text, Button} from 'native-base';
import {View} from 'react-native';
import {
LinkingOptions,
NavigationContainer,
useNavigation,
} from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
const styles = StyleSheet.create({
container: {
height: '100%',
flex: 1,
},
});
export const linking: LinkingOptions<{
Maps: string;
Home: string;
Chat: string;
Settings: string;
}> = {
prefixes: ['http://'],
config: {
screens: {
Maps: 'mapss',
Home: 'account',
Chat: 'chats',
Settings: 'settingss',
},
},
};
export type HomeStackNavigatorParamList = {
Account: undefined;
Maps: undefined;
Chat: undefined;
Test: undefined;
Settings: undefined;
};
export type HomeScreenNavigationProp =
NativeStackNavigationProp<HomeStackNavigatorParamList>;
export default function Navigation() {
const theme = useSelector(
(state: RootState) => state.appVariables.preferences.theme,
);
const dispatch = useDispatch();
return (
<Tab.Navigator screenOptions={{headerShown: false}}>
<Tab.Screen name="Home" component={HomeStackScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen
name="Chat"
options={{headerShown: true, tabBarStyle: {display: 'none'}}}
component={ChatScreen}
/>
</Tab.Navigator>
);
}
function ChatScreen() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Chat!</Text>
</View>
);
}
function HomeScreen() {
const navigation = useNavigation<HomeScreenNavigationProp>();
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Home screen</Text>
<Button onPress={() => navigation.navigate('Test')}>Go to Test</Button>
</View>
);
}
export type SettingsStackNavigatorParamList = {
Chat: undefined;
Settings: undefined;
};
export type SettingsScreenNavigationProp =
NativeStackNavigationProp<SettingsStackNavigatorParamList>;
function SettingsScreen() {
const navigation = useNavigation<SettingsScreenNavigationProp>();
const navigation2 = useNavigation<HomeScreenNavigationProp>();
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Settings screen</Text>
<Button onPress={() => navigation2.navigate('Maps')}>Go to Chat</Button>
</View>
);
}
const HomeStack = createNativeStackNavigator<HomeStackNavigatorParamList>();
function HomeStackScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen
name="Maps"
options={{headerShown: false}}
component={HomeScreen}
/>
<HomeStack.Screen name="Test" component={ChatScreen} />
</HomeStack.Navigator>
);
}
const SettingsStack = createNativeStackNavigator<HomeStackNavigatorParamList>();
function SettingsStackScreen() {
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
<SettingsStack.Screen name="Chat" component={ChatScreen} />
</SettingsStack.Navigator>
);
}
const Tab = createBottomTabNavigator();

View File

@ -0,0 +1,30 @@
import {AccountName, Username} from '@caj/helper/types';
import {RootState} from '@caj/redux/store';
import {HStack, Text} from 'native-base';
import {useSelector} from 'react-redux';
export default function NameDisplay(props: {
UserName: Username;
AccountName: AccountName;
fontSize?: number;
}) {
const theme = useSelector(
(state: RootState) => state.appVariables.preferences.theme,
);
const fontSize = props.fontSize || 15;
const lineHeight = fontSize * 1.25;
return (
<HStack space={fontSize / 30} alignItems={'flex-end'}>
<Text fontSize={fontSize} color="primary.400">
{props.UserName !== '' ? props.UserName : '----'}
</Text>
<Text
fontSize={fontSize / 1.5}
fontFamily={'Outfit-Light'}
color="light.400">
{props.AccountName !== '' ? props.AccountName : '----'}
</Text>
</HStack>
);
}

View File

@ -1,4 +1,4 @@
import {AccountName, Username} from '@caj/helper/types';
import {AccountName, Username} from '@caj/configs/types';
import {RootState} from '@caj/redux/store';
import {HStack, Text} from 'native-base';
import {useSelector} from 'react-redux';

View File

@ -0,0 +1,960 @@
import {RegisterProcess, ThemeMode} from '@caj/configs/appVar';
import {appVarActions} from '@caj/configs/appVarReducer';
import {defaultHeaderStyle} from '@caj/configs/colors';
import {SlideFromLeftView} from '@caj/helper/animations';
import {saveVarChanges} from '@caj/helper/appData';
import {apiBackendRequest, makeRequest} from '@caj/helper/request';
import {
accountNameOptions,
EMail,
emailOptions,
passwordOptions,
userNameOptions,
XToken,
} from '@caj/helper/types';
import {RootScreenNavigationProp} from '@caj/Navigation';
import {RootState, store} from '@caj/redux/store';
import {useNavigation} from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {
Box,
Button,
Center,
Container,
FormControl,
Heading,
HStack,
Icon,
IconButton,
Input,
Pressable,
ScrollView,
Spinner,
Text,
useColorModeValue,
useTheme,
useToast,
VStack,
WarningOutlineIcon,
} from 'native-base';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import {baseFontSize} from 'native-base/lib/typescript/theme/tools';
import {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import reactStringReplace from 'react-string-replace';
import ConfirmationCodeField from './ConfirmationCodeField';
import NameDisplay from './NameDisplay';
import showToast from './Toast';
import {NativeSyntheticEvent, TextInputFocusEventData} from 'react-native';
const validateEmail = (email: EMail) => {
return emailOptions.isAllowed(email);
};
export default function NotLoggedIn() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const theme = useSelector(
(state: RootState) => state.appVariables.preferences.theme,
);
const toast = useToast();
const navigation = useNavigation<RootScreenNavigationProp>();
return (
<Box alignItems="center" mt={5}>
<Text color="primary.400">{lang.appName}</Text>
<Text color="white.100">{lang.appNameDesc}</Text>
<VStack mt={5} space={4} alignItems="center">
<Button
w="72"
colorScheme="primary"
rounded="xl"
_text={{fontSize: 'xl'}}
onPress={() => {
navigation.navigate('Register', {screen: 'RegStepOne'});
}}>
Sign up
</Button>
<Button
w="72"
colorScheme="black"
variant={theme === ThemeMode.Darkest ? 'outline' : 'subtle'}
rounded="xl"
_text={{fontSize: 'xl', color: 'white.900'}}>
Log in
</Button>
</VStack>
</Box>
);
}
export function RegisterScreenAnim(props: any) {
return (
<SlideFromLeftView>
<RegisterScreen {...props} />
</SlideFromLeftView>
);
}
export type LoginStackNavigatorParamList = {
RegStepOne: undefined;
RegStepTwo: undefined;
RegStepFinal: undefined;
};
const LoginStack = createNativeStackNavigator<LoginStackNavigatorParamList>();
export type LoginScreenNavigationProp =
NativeStackNavigationProp<LoginStackNavigatorParamList>;
function RegisterScreen() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const theme = useSelector(
(state: RootState) => state.appVariables.preferences.theme,
);
return (
<LoginStack.Navigator>
<LoginStack.Screen
name="RegStepOne"
options={{
animation: 'slide_from_left',
title: lang.account.registration.registration,
headerShown: true,
...defaultHeaderStyle(theme, 'registration'),
}}
component={StepOne}
/>
<LoginStack.Screen
name="RegStepTwo"
options={{
animation: 'slide_from_right',
title: lang.account.registration.registration,
headerShown: true,
...defaultHeaderStyle(theme, 'registration'),
}}
component={StepTwo}
/>
<LoginStack.Screen
name="RegStepFinal"
options={{
animation: 'slide_from_right',
title: lang.account.registration.registration,
headerShown: true,
...defaultHeaderStyle(theme, 'registration'),
}}
component={StepFinal}
/>
</LoginStack.Navigator>
);
}
function Agreement() {
const toast = useToast();
const lang = useSelector((state: RootState) => state.appVariables.lang);
const textColor = useColorModeValue('blue.700', 'cyan.400');
let replacedText = reactStringReplace(
lang.account.registration.info,
'${TermsOfUse}',
(match, i) => (
<Text
key={match + i}
color={textColor}
bold
onPress={() => {
showToast(toast, {
title: lang.account.registration.termsOfUse,
variant: 'solid',
status: 'info',
description: undefined,
isClosable: true,
rest: {colorScheme: 'primary'},
});
}}>
{lang.account.registration.termsOfUse}
</Text>
),
);
replacedText = reactStringReplace(
replacedText,
'${privacyPolicy}',
(match, i) => (
<Text
key={match + i}
color={textColor}
bold
onPress={() => {
showToast(toast, {
title: lang.account.registration.privacyPolicy,
variant: 'solid',
status: 'info',
description: undefined,
isClosable: true,
rest: {colorScheme: 'primary'},
});
}}>
{lang.account.registration.privacyPolicy}
</Text>
),
);
return (
<Text textAlign={'justify'} color={'white.900'}>
{replacedText}
</Text>
);
}
function resendMail(email: EMail, toast: any): Promise<XToken> {
return new Promise<XToken>((resolve, reject) => {
makeRequest({
path: apiBackendRequest.REGISTER_RESEND_MAIL,
requestHeader: {},
request: {
Email: email,
},
//response: {
// XToken: undefined,
//},
})
.then(resp => {
console.log(1);
let token =
store.getState().appVariables.preferences.RegisterProcess.XToken;
if (token !== undefined /*resp.response.XToken !== undefined*/) {
showToast(toast, {
title: store.getState().appVariables.lang.info,
variant: 'solid',
status: 'info',
description:
store.getState().appVariables.lang.account.registration.stepTwo
.resend[2],
isClosable: true,
rest: {},
});
resolve(token);
} else {
reject(500);
showToast(toast, {
title: store.getState().appVariables.lang.error,
variant: 'solid',
status: 'error',
description: 'XToken is undefined',
isClosable: true,
rest: {},
});
}
})
.catch(resp => {
let text = 'unknown error ' + resp.status;
if (resp.status !== undefined) {
const _text =
store.getState().appVariables.lang.account.registration.stepTwo
.resendError[resp.status as number];
if (_text !== undefined) text = _text;
}
showToast(toast, {
title: store.getState().appVariables.lang.error,
variant: 'solid',
status: 'error',
description: text,
isClosable: true,
rest: {},
});
reject(resp.status);
});
});
}
function StepOne() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const regPro = useSelector(
(state: RootState) => state.appVariables.preferences.RegisterProcess,
);
const dispatch = useDispatch();
const toast = useToast();
const navigation = useNavigation<RootScreenNavigationProp>();
const initNoErrors = {
wrongFormat: false,
alreadyExists: false,
noEntered: false,
unknown: undefined,
};
const [errors, setErrors] = useState(initNoErrors);
const isError =
errors.wrongFormat ||
errors.alreadyExists ||
errors.noEntered ||
errors.unknown !== undefined;
const errorText = () => {
if (errors.wrongFormat) {
return lang.account.registration.stepOne.addressInvalid;
}
if (errors.alreadyExists) {
return lang.account.registration.stepOne.addressExists;
}
if (errors.noEntered) {
return lang.account.registration.stepOne.noMailEntered;
}
if (errors.unknown !== undefined) {
return errors.unknown;
}
};
const [isLoading, setLoading] = useState(false);
const [values, setValues] = useState({email: regPro.EMail});
useEffect(() => {
if (regPro.isRegistering === 'stepTwo') {
setLoading(true);
setErrors(initNoErrors);
setTimeout(nextStep, 500);
} else if (regPro.isRegistering === 'stepFinal') {
setLoading(true);
setErrors(initNoErrors);
setTimeout(nextStep, 500);
}
}, []);
const nextStep = () => {
setLoading(true);
setErrors(initNoErrors);
makeRequest({
path: apiBackendRequest.REGISTER_STEP_1,
requestHeader: {},
request: {
Email: values.email,
},
response: {
XToken: undefined,
},
})
.then(resp => {
let rp = {...regPro};
rp.isRegistering = 'stepTwo';
rp.EMail = values.email;
rp.XToken = resp.response.XToken;
dispatch(appVarActions.setRegisterProcess(rp));
saveVarChanges();
showToast(toast, {
title: lang.account.registration.stepOne.success,
variant: 'solid',
status: 'success',
description: undefined,
isClosable: true,
rest: {colorScheme: 'primary'},
});
navigation.navigate('Register', {screen: 'RegStepTwo'});
setLoading(false);
})
.catch(resp => {
if (resp.status === 401 || resp.status === 204) {
if (regPro.XToken !== undefined) {
let rp = {...regPro};
if (resp.status === 401) {
rp.isRegistering = 'stepTwo';
rp.EMail = values.email;
dispatch(appVarActions.setRegisterProcess(rp));
saveVarChanges();
navigation.navigate('Register', {screen: 'RegStepTwo'});
} else if (resp.status === 204) {
rp.isRegistering = 'stepFinal';
rp.EMail = values.email;
dispatch(appVarActions.setRegisterProcess(rp));
saveVarChanges();
navigation.navigate('Register', {screen: 'RegStepFinal'});
}
setLoading(false);
} else {
resendMail(values.email, toast)
.then(() => {
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}
} else {
showToast(toast, {
title: 'Error',
variant: 'solid',
status: 'error',
description: resp.status,
isClosable: true,
rest: {colorScheme: 'primary'},
});
setLoading(false);
}
});
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<Box alignItems="center" mt={5}>
<FormControl
w="75%"
maxW="350px"
isRequired
isDisabled={isLoading}
isInvalid={isError}>
<FormControl.Label>
{lang.account.registration.stepOne.title}
</FormControl.Label>
<Input
autoFocus
placeholder={lang.account.registration.stepOne.title}
value={values.email}
autoCapitalize={'none'}
maxLength={emailOptions.maxLength}
keyboardType={'email-address'}
onChangeText={text => {
const mail = text.replaceAll(' ', '');
setValues({email: mail});
if (errors.noEntered && mail !== '') {
let err = errors;
err.noEntered = false;
setErrors({...err});
}
if (errors.wrongFormat && validateEmail(mail)) {
let err = errors;
err.wrongFormat = false;
setErrors({...err});
}
if (errors.alreadyExists) {
let err = errors;
err.alreadyExists = false;
setErrors({...err});
}
}}
/>
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
{errorText()}
</FormControl.ErrorMessage>
</FormControl>
<Container marginY={5}>
<Agreement />
</Container>
<Button
w="75%"
maxW="350px"
colorScheme="primary"
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={() => {
if (values.email === '') {
let err = errors;
err.noEntered = true;
setErrors({...err});
} else if (validateEmail(values.email)) {
nextStep();
} else {
let err = errors;
err.wrongFormat = true;
setErrors({...err});
}
}}>
{lang.account.registration.stepOne.button}
</Button>
</Box>
</ScrollView>
);
}
function StepTwo() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const regPro = useSelector(
(state: RootState) => state.appVariables.preferences.RegisterProcess,
);
const dispatch = useDispatch();
const cellCount = 6;
const {colors} = useTheme();
const toast = useToast();
const navigation = useNavigation<RootScreenNavigationProp>();
const initNoErrors = {
noEntered: false,
};
const [errors, setErrors] = useState(initNoErrors);
const [isLoading, setLoading] = useState(false);
const [values, setValues] = useState({code: ''});
const headerText = () => {
return reactStringReplace(
lang.account.registration.stepTwo.title,
'${EMail}',
(match, i) => (
<Text key={match + i} color={'primary.400'}>
{regPro.EMail}
</Text>
),
);
};
const resendText = () => {
return reactStringReplace(
lang.account.registration.stepTwo.resend[0],
'${resend}',
(match, i) => (
<Text
key={match + i}
color={'primary.400'}
onPress={() => {
setLoading(true);
resendMail(
store.getState().appVariables.preferences.RegisterProcess.EMail,
toast,
)
.then(() => {
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}}>
{lang.account.registration.stepTwo.resend[1]}
</Text>
),
);
};
const validate = (text: string) => {
setLoading(true);
setTimeout(() => {
makeRequest({
path: apiBackendRequest.REGISTER_STEP_2,
requestHeader: {},
requestGET: {':verifyId': text, ':xToken': regPro.XToken || ''},
})
.then(resp => {
let rp = {...regPro};
rp.isRegistering = 'stepFinal';
dispatch(appVarActions.setRegisterProcess(rp));
saveVarChanges();
showToast(toast, {
title: lang.account.registration.stepTwo.success,
variant: 'solid',
status: 'success',
description: undefined,
isClosable: true,
rest: {colorScheme: 'primary'},
});
navigation.navigate('Register', {screen: 'RegStepFinal'});
setLoading(false);
})
.catch(resp => {
let text = 'unknown error ' + resp.status;
if (resp.status !== undefined) {
const _text =
lang.account.registration.stepTwo.verificationError[
resp.status as number
];
if (_text !== undefined) text = _text;
}
showToast(toast, {
title: lang.error,
variant: 'solid',
status: 'error',
description: text,
isClosable: true,
rest: {},
});
setLoading(false);
if (resp.status === 422) {
let rp = {...regPro};
rp.isRegistering = false;
dispatch(appVarActions.setRegisterProcess(rp));
saveVarChanges();
navigation.navigate('Register', {screen: 'RegStepOne'});
}
});
}, 500);
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<Box alignItems="center" mt={5}>
<Center w="75%" maxW="1000px">
<Text>{headerText()}</Text>
<Box>
<FormControl isDisabled={isLoading} isInvalid={errors.noEntered}>
<ConfirmationCodeField
cellCount={cellCount}
charType="number"
disabled={isLoading}
rest={{mt: 5}}
onChange={(text: string) => {
setValues({code: text});
setErrors(initNoErrors);
}}
onFinish={(text: string) => validate(text)}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}>
{lang.account.registration.stepTwo.noCodeEntered}
</FormControl.ErrorMessage>
</FormControl>
</Box>
</Center>
<Container marginY={5}>
<Agreement />
</Container>
<Button
w="75%"
maxW="350px"
colorScheme="primary"
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={() => {
if (values.code.length === cellCount) {
validate(values.code);
} else {
let err = {...errors};
err.noEntered = true;
setErrors(err);
}
}}>
{lang.account.registration.stepTwo.button}
</Button>
<Container marginY={5}>
<Text textAlign={'justify'} color={'white.900'}>
{resendText()}
</Text>
</Container>
</Box>
</ScrollView>
);
}
function StepFinal() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const regPro = useSelector(
(state: RootState) => state.appVariables.preferences.RegisterProcess,
);
const dispatch = useDispatch();
const {colors} = useTheme();
const toast = useToast();
const navigation = useNavigation<RootScreenNavigationProp>();
const [isLoading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
interface inputElementType {
label: string;
input: string;
autoCapitalize: 'none' | 'words';
errorIndex: 'none' | string;
errorTextObject: any;
isPassword: boolean | 'passwordRepeat';
minLength: number;
maxLength: number;
onTextChange: any;
textChangeTimeout: number;
isAllowed: any;
}
const accountNameRef = useRef(setTimeout(() => {}));
const accountNameFetchRef = useRef(setTimeout(() => {}));
const accountName = {
label: lang.account.registration.stepFinal.accountName,
input: '',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.accountNameError,
minLength: accountNameOptions.minLength,
maxLength: accountNameOptions.maxLength,
isAllowed: accountNameOptions.isAllowed,
isPassword: false,
onTextChange: (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
let text = e.nativeEvent.text;
let self = accountName;
clearTimeout(accountNameRef.current);
let obj = {...valuesAccountName};
accountNameRef.current = setTimeout(() => {
obj.input = text;
if (text.length < self.minLength) obj.errorIndex = 'tooShort';
else if (text.length > self.maxLength) obj.errorIndex = 'tooLong';
else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid';
else obj.errorIndex = 'none';
setValuesAccountName(obj);
}, 50);
clearTimeout(accountNameFetchRef.current);
accountNameFetchRef.current = setTimeout(() => {
console.log(obj);
if (obj.errorIndex === 'none') {
makeRequest({
path: apiBackendRequest.REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK,
request: {AccountName: obj.input},
})
.then(resp => {
console.log('OK');
})
.catch(resp => {
if (resp.status !== undefined) {
obj.errorIndex = resp.status;
setValuesAccountName(obj);
}
});
}
}, 750);
},
} as inputElementType;
const [valuesAccountName, setValuesAccountName] = useState(accountName);
const userNameRef = useRef(setTimeout(() => {}));
const userName = {
label: lang.account.registration.stepFinal.userName,
input: '',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.userNameError,
minLength: userNameOptions.minLength,
maxLength: userNameOptions.maxLength,
isAllowed: userNameOptions.isAllowed,
isPassword: false,
onTextChange: (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
let text = e.nativeEvent.text;
let self = userName;
clearTimeout(userNameRef.current);
userNameRef.current = setTimeout(() => {
let obj = {...valuesUserName};
obj.input = text;
if (text.length < self.minLength) obj.errorIndex = 'tooShort';
else if (text.length > self.maxLength) obj.errorIndex = 'tooLong';
else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid';
else obj.errorIndex = 'none';
setValuesUserName(obj);
}, 50);
},
} as inputElementType;
const [valuesUserName, setValuesUserName] = useState(userName);
const passwordRef = useRef(setTimeout(() => {}));
const password = {
label: lang.account.registration.stepFinal.password,
input: '',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.passwordError,
minLength: passwordOptions.minLength,
maxLength: passwordOptions.maxLength,
isAllowed: passwordOptions.isAllowed,
isPassword: true,
onTextChange: (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
let text = e.nativeEvent.text;
let self = password;
clearTimeout(passwordRef.current);
passwordRef.current = setTimeout(() => {
let obj = {...valuesPassword};
obj.input = text;
if (text.length < self.minLength) obj.errorIndex = 'tooShort';
else if (text.length > self.maxLength) obj.errorIndex = 'tooLong';
else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid';
else obj.errorIndex = 'none';
setValuesPassword(obj);
let objRe = {...valuesPasswordRe};
if (text !== valuesPasswordRe.input) objRe.errorIndex = 'noMatch';
else objRe.errorIndex = 'none';
setValuesPasswordRe(objRe);
}, 50);
},
} as inputElementType;
const [valuesPassword, setValuesPassword] = useState(password);
const passwordReRef = useRef(setTimeout(() => {}));
const passwordRe = {
label: lang.account.registration.stepFinal.passwordRepeat,
input: '',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.passwordError,
minLength: passwordOptions.minLength,
maxLength: passwordOptions.maxLength,
isAllowed: passwordOptions.isAllowed,
isPassword: 'passwordRepeat',
onTextChange: (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
let text = e.nativeEvent.text;
let self = passwordRe;
clearTimeout(passwordReRef.current);
passwordReRef.current = setTimeout(() => {
let obj = {...valuesPasswordRe};
obj.input = text;
console.log(text, valuesPassword.input);
if (text !== valuesPassword.input) obj.errorIndex = 'noMatch';
else obj.errorIndex = 'none';
setValuesPasswordRe(obj);
}, 50);
},
} as inputElementType;
const [valuesPasswordRe, setValuesPasswordRe] = useState(passwordRe);
const inputElement = (
val: inputElementType,
set: React.Dispatch<React.SetStateAction<inputElementType>>,
valConst: inputElementType,
autofocus?: boolean,
) => {
const isPassword =
val.isPassword === true || val.isPassword === 'passwordRepeat';
return (
<FormControl
w="75%"
maxW="350px"
isRequired
isDisabled={isLoading}
isInvalid={val.errorIndex !== 'none'}>
<FormControl.Label>{val.label}</FormControl.Label>
<Input
autoFocus={autofocus}
type={isPassword && showPassword === false ? 'password' : 'text'}
placeholder={''}
keyboardType={'default'}
onChange={valConst.onTextChange}
InputRightElement={
isPassword ? (
<IconButton
mr="2"
onPress={() => setShowPassword(!showPassword)}
icon={
<MaterialIcons
size={20}
name={showPassword ? 'visibility' : 'visibility-off'}
/>
}
borderRadius="full"
/>
) : undefined
}
/>
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
{val.errorTextObject[val.errorIndex] !== undefined
? val.errorTextObject[val.errorIndex]
.replaceAll('$(minLength)', val.minLength)
.replaceAll('$(maxLength)', val.maxLength)
: val.errorTextObject[val.errorIndex] !== 'none'
? lang.error
: null}
</FormControl.ErrorMessage>
</FormControl>
);
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<VStack space={3} alignItems="center" mt={5}>
<Container maxWidth={'100%'} alignItems={'center'}>
<Center>
<Text>{lang.account.registration.stepFinal.displayName}</Text>
<NameDisplay
UserName={
valuesUserName.input !== ''
? valuesUserName.input
: lang.account.registration.stepFinal.userName
}
AccountName={
valuesAccountName.input !== ''
? valuesAccountName.input
: lang.account.registration.stepFinal.accountName
}
/>
</Center>
</Container>
{inputElement(valuesUserName, setValuesUserName, userName)}
{inputElement(valuesAccountName, setValuesAccountName, accountName)}
{inputElement(valuesPassword, setValuesPassword, password)}
{inputElement(valuesPasswordRe, setValuesPasswordRe, passwordRe)}
</VStack>
<Center>
<Container mt={5}>
<Agreement />
</Container>
<Button
marginY={5}
w="75%"
maxW="350px"
colorScheme="primary"
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={() => {}}>
{lang.account.registration.stepFinal.button}
</Button>
</Center>
</ScrollView>
);
}

View File

@ -11,7 +11,7 @@ import {
passwordOptions,
userNameOptions,
XToken,
} from '@caj/helper/types';
} from '@caj/configs/types';
import {RootScreenNavigationProp} from '@caj/Navigation';
import {RootState, store} from '@caj/redux/store';
import {useNavigation} from '@react-navigation/native';
@ -25,25 +25,20 @@ import {
Center,
Container,
FormControl,
Heading,
HStack,
Icon,
IconButton,
Input,
Pressable,
ScrollView,
Spinner,
Text,
useColorModeValue,
useTheme,
useToast,
VStack,
WarningOutlineIcon,
Progress,
} from 'native-base';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import {baseFontSize} from 'native-base/lib/typescript/theme/tools';
import {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import reactStringReplace from 'react-string-replace';
@ -52,6 +47,17 @@ import NameDisplay from './NameDisplay';
import showToast from './Toast';
import {NativeSyntheticEvent, TextInputFocusEventData} from 'react-native';
import PasswordQualityCalculator from '@caj/helper/password-quality-calculator/PasswordQualityCalculator';
// [ optional ] list of about 10000 most common passwords, 86kb (gzip 32kb)
import MostPopularPasswords from '@caj/helper/password-quality-calculator/MostPopularPasswords';
import MyUserManager from '@caj/user/MyUserManager';
const Buffer = require('buffer').Buffer;
// Load the popular passwords list
PasswordQualityCalculator.PopularPasswords.load(MostPopularPasswords);
const validateEmail = (email: EMail) => {
return emailOptions.isAllowed(email);
};
@ -86,7 +92,10 @@ export default function NotLoggedIn() {
colorScheme="black"
variant={theme === ThemeMode.Darkest ? 'outline' : 'subtle'}
rounded="xl"
_text={{fontSize: 'xl', color: 'white.900'}}>
_text={{fontSize: 'xl', color: 'white.900'}}
onPress={() => {
navigation.navigate('Register', {screen: 'Login'});
}}>
Log in
</Button>
</VStack>
@ -106,6 +115,7 @@ export type LoginStackNavigatorParamList = {
RegStepOne: undefined;
RegStepTwo: undefined;
RegStepFinal: undefined;
Login: undefined;
};
const LoginStack = createNativeStackNavigator<LoginStackNavigatorParamList>();
@ -151,6 +161,16 @@ function RegisterScreen() {
}}
component={StepFinal}
/>
<LoginStack.Screen
name="Login"
options={{
animation: 'slide_from_right',
title: lang.account.login.title,
headerShown: true,
...defaultHeaderStyle(theme, 'registration'),
}}
component={Login}
/>
</LoginStack.Navigator>
);
}
@ -218,20 +238,35 @@ function resendMail(email: EMail, toast: any): Promise<XToken> {
return new Promise<XToken>((resolve, reject) => {
makeRequest({
path: apiBackendRequest.REGISTER_RESEND_MAIL,
requestHeader: {},
requestHeader: {
'X-Token':
store.getState().appVariables.preferences.RegisterProcess.XToken,
},
request: {
Email: email,
},
//response: {
// XToken: undefined,
//},
response: {
XToken: undefined,
},
})
.then(resp => {
console.log(1);
let token =
store.getState().appVariables.preferences.RegisterProcess.XToken;
if (token !== undefined /*resp.response.XToken !== undefined*/) {
if (token === undefined) token = '';
if (resp.response.XToken !== undefined || token !== undefined) {
if (resp.response.XToken !== undefined) {
token = resp.response.XToken;
let regPro = {
...store.getState().appVariables.preferences.RegisterProcess,
};
regPro.XToken = token;
store.dispatch(appVarActions.setRegisterProcess(regPro));
saveVarChanges();
}
showToast(toast, {
title: store.getState().appVariables.lang.info,
variant: 'solid',
@ -257,7 +292,7 @@ function resendMail(email: EMail, toast: any): Promise<XToken> {
}
})
.catch(resp => {
let text = 'unknown error ' + resp.status;
let text = 'Error ' + resp.status;
if (resp.status !== undefined) {
const _text =
store.getState().appVariables.lang.account.registration.stepTwo
@ -342,6 +377,12 @@ function StepOne() {
setLoading(true);
setErrors(initNoErrors);
let rp = {...regPro};
if (rp.EMail !== values.email) {
rp.XToken = undefined;
}
makeRequest({
path: apiBackendRequest.REGISTER_STEP_1,
requestHeader: {},
@ -353,7 +394,6 @@ function StepOne() {
},
})
.then(resp => {
let rp = {...regPro};
rp.isRegistering = 'stepTwo';
rp.EMail = values.email;
rp.XToken = resp.response.XToken;
@ -376,8 +416,6 @@ function StepOne() {
.catch(resp => {
if (resp.status === 401 || resp.status === 204) {
if (regPro.XToken !== undefined) {
let rp = {...regPro};
if (resp.status === 401) {
rp.isRegistering = 'stepTwo';
rp.EMail = values.email;
@ -400,26 +438,51 @@ function StepOne() {
} else {
resendMail(values.email, toast)
.then(() => {
navigation.navigate('Register', {screen: 'RegStepTwo'});
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}
return;
} else if (resp.status === 422) {
showToast(toast, {
title: lang.error,
variant: 'solid',
status: 'error',
description: lang.account.registration.stepOne.addressExists,
isClosable: true,
rest: {colorScheme: 'primary'},
});
} else {
showToast(toast, {
title: 'Error',
title: lang.error,
variant: 'solid',
status: 'error',
description: resp.status,
isClosable: true,
rest: {colorScheme: 'primary'},
});
setLoading(false);
}
setLoading(false);
});
};
const onButtonPress = () => {
if (values.email === '') {
let err = errors;
err.noEntered = true;
setErrors({...err});
} else if (validateEmail(values.email)) {
nextStep();
} else {
let err = errors;
err.wrongFormat = true;
setErrors({...err});
}
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<Box alignItems="center" mt={5}>
@ -434,8 +497,10 @@ function StepOne() {
</FormControl.Label>
<Input
autoFocus
autoComplete="email"
placeholder={lang.account.registration.stepOne.title}
value={values.email}
onSubmitEditing={onButtonPress}
autoCapitalize={'none'}
maxLength={emailOptions.maxLength}
keyboardType={'email-address'}
@ -475,19 +540,7 @@ function StepOne() {
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={() => {
if (values.email === '') {
let err = errors;
err.noEntered = true;
setErrors({...err});
} else if (validateEmail(values.email)) {
nextStep();
} else {
let err = errors;
err.wrongFormat = true;
setErrors({...err});
}
}}>
onPress={onButtonPress}>
{lang.account.registration.stepOne.button}
</Button>
</Box>
@ -588,7 +641,7 @@ function StepTwo() {
setLoading(false);
})
.catch(resp => {
let text = 'unknown error ' + resp.status;
let text = 'Error ' + resp.status;
if (resp.status !== undefined) {
const _text =
lang.account.registration.stepTwo.verificationError[
@ -704,6 +757,7 @@ function StepFinal() {
onTextChange: any;
textChangeTimeout: number;
isAllowed: any;
autoComplete?: 'name' | 'username-new' | 'password-new';
}
const accountNameRef = useRef(setTimeout(() => {}));
@ -712,6 +766,7 @@ function StepFinal() {
const accountName = {
label: lang.account.registration.stepFinal.accountName,
input: '',
autoComplete: 'username-new',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.accountNameError,
minLength: accountNameOptions.minLength,
@ -734,28 +789,25 @@ function StepFinal() {
else obj.errorIndex = 'none';
setValuesAccountName(obj);
accountNameFetchRef.current = setTimeout(() => {
if (obj.errorIndex === 'none') {
makeRequest({
path: apiBackendRequest.REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK,
request: {AccountName: obj.input},
})
.then(resp => {})
.catch(resp => {
if (resp.status !== undefined) {
obj.errorIndex = resp.status;
setValuesAccountName({...obj});
}
});
}
}, 750);
}, 50);
clearTimeout(accountNameFetchRef.current);
accountNameFetchRef.current = setTimeout(() => {
console.log(obj);
if (obj.errorIndex === 'none') {
makeRequest({
path: apiBackendRequest.REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK,
request: {AccountName: obj.input},
})
.then(resp => {
console.log('OK');
})
.catch(resp => {
if (resp.status !== undefined) {
obj.errorIndex = resp.status;
setValuesAccountName(obj);
}
});
}
}, 750);
},
} as inputElementType;
const [valuesAccountName, setValuesAccountName] = useState(accountName);
@ -765,6 +817,7 @@ function StepFinal() {
const userName = {
label: lang.account.registration.stepFinal.userName,
input: '',
autoComplete: 'name',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.userNameError,
minLength: userNameOptions.minLength,
@ -797,6 +850,7 @@ function StepFinal() {
const password = {
label: lang.account.registration.stepFinal.password,
input: '',
autoComplete: 'password-new',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.passwordError,
minLength: passwordOptions.minLength,
@ -813,28 +867,56 @@ function StepFinal() {
let obj = {...valuesPassword};
obj.input = text;
let PasswordQuality = PasswordQualityCalculator(text);
setPasswordQuality(PasswordQuality);
if (text.length < self.minLength) obj.errorIndex = 'tooShort';
else if (text.length > self.maxLength) obj.errorIndex = 'tooLong';
else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid';
else if (passwordOptions.minBits >= PasswordQuality)
obj.errorIndex = 'weak';
else obj.errorIndex = 'none';
setValuesPassword(obj);
let objRe = {...valuesPasswordRe};
if (text !== valuesPasswordRe.input) objRe.errorIndex = 'noMatch';
else objRe.errorIndex = 'none';
if (valuesPasswordRe.input !== '') {
if (text !== valuesPasswordRe.input) objRe.errorIndex = 'noMatch';
else objRe.errorIndex = 'none';
setValuesPasswordRe(objRe);
setValuesPasswordRe(objRe);
}
}, 50);
},
} as inputElementType;
const [valuesPassword, setValuesPassword] = useState(password);
const [passwordQuality, setPasswordQuality] = useState(0);
let passwordQualityIndex = 0;
if (passwordQuality >= 128) passwordQualityIndex = 4;
else if (passwordQuality >= 100) passwordQualityIndex = 3;
else if (passwordQuality >= 80) passwordQualityIndex = 2;
else if (passwordQuality >= 50) passwordQualityIndex = 1;
let passwordQualityPercent = (passwordQuality / 128.0) * 100;
passwordQualityPercent =
passwordQualityPercent >= 100.0 ? 100 : passwordQualityPercent;
const passwordQualityColor = 'hsl(' + passwordQualityPercent + ', 100%, 50%)';
const passwordQualityText =
lang.account.registration.stepFinal.passwordQuality.replace(
'${quality}',
lang.account.registration.stepFinal.passwordQualityList[
passwordQualityIndex
],
);
const passwordReRef = useRef(setTimeout(() => {}));
const passwordRe = {
label: lang.account.registration.stepFinal.passwordRepeat,
input: '',
autoComplete: 'password-new',
errorIndex: 'none',
errorTextObject: lang.account.registration.stepFinal.passwordError,
minLength: passwordOptions.minLength,
@ -850,7 +932,6 @@ function StepFinal() {
passwordReRef.current = setTimeout(() => {
let obj = {...valuesPasswordRe};
obj.input = text;
console.log(text, valuesPassword.input);
if (text !== valuesPassword.input) obj.errorIndex = 'noMatch';
else obj.errorIndex = 'none';
@ -873,12 +954,13 @@ function StepFinal() {
return (
<FormControl
w="75%"
maxW="350px"
maxW={350}
isRequired
isDisabled={isLoading}
isInvalid={val.errorIndex !== 'none'}>
<FormControl.Label>{val.label}</FormControl.Label>
<Input
autoComplete={val.autoComplete}
autoFocus={autofocus}
type={isPassword && showPassword === false ? 'password' : 'text'}
placeholder={''}
@ -888,10 +970,12 @@ function StepFinal() {
isPassword ? (
<IconButton
mr="2"
focusable={false}
onPress={() => setShowPassword(!showPassword)}
icon={
<MaterialIcons
size={20}
color={colors.white[200]}
name={showPassword ? 'visibility' : 'visibility-off'}
/>
}
@ -904,8 +988,8 @@ function StepFinal() {
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
{val.errorTextObject[val.errorIndex] !== undefined
? val.errorTextObject[val.errorIndex]
.replaceAll('$(minLength)', val.minLength)
.replaceAll('$(maxLength)', val.maxLength)
.replaceAll('${minLength}', val.minLength)
.replaceAll('${maxLength}', val.maxLength)
: val.errorTextObject[val.errorIndex] !== 'none'
? lang.error
: null}
@ -937,6 +1021,20 @@ function StepFinal() {
{inputElement(valuesUserName, setValuesUserName, userName)}
{inputElement(valuesAccountName, setValuesAccountName, accountName)}
{inputElement(valuesPassword, setValuesPassword, password)}
<Box w="75%" maxW={350}>
<Text mx={4} mb={1} fontSize={13} color="light.400">
{passwordQualityText}
</Text>
<Progress
value={passwordQualityPercent}
mx={4}
_filledTrack={{
bg: passwordQualityColor,
}}
/>
</Box>
{inputElement(valuesPasswordRe, setValuesPasswordRe, passwordRe)}
</VStack>
<Center>
@ -951,10 +1049,400 @@ function StepFinal() {
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={() => {}}>
onPress={() => {
function showToastNow(text: string) {
showToast(toast, {
title: text,
variant: 'solid',
status: 'error',
description: undefined,
isClosable: true,
rest: {},
});
}
valuesUserName.onTextChange({
nativeEvent: {text: valuesUserName.input},
});
valuesAccountName.onTextChange({
nativeEvent: {text: valuesAccountName.input},
});
valuesPassword.onTextChange({
nativeEvent: {text: valuesPassword.input},
});
let obj = {...valuesPasswordRe};
if (obj.input !== valuesPassword.input) obj.errorIndex = 'noMatch';
else obj.errorIndex = 'none';
setValuesPasswordRe(obj);
setLoading(true);
setTimeout(() => {
if (
valuesAccountName.input === '' ||
valuesUserName.input === '' ||
valuesPassword.input === '' ||
valuesPasswordRe.input === '' ||
valuesUserName.errorIndex !== 'none' ||
valuesAccountName.errorIndex !== 'none' ||
valuesPassword.errorIndex !== 'none' ||
valuesPasswordRe.errorIndex !== 'none'
) {
/*showToastNow(
lang.account.registration.stepFinal.noAllFieldsEntered,
);*/
setLoading(false);
return;
}
let base64PW = new Buffer(valuesPassword.input).toString(
'base64',
);
let xToken =
store.getState().appVariables.preferences.RegisterProcess
.XToken;
if (xToken === undefined) xToken = '';
makeRequest({
path: apiBackendRequest.REGISTER_STEP_FINAL,
requestHeader: {
'X-Token': xToken,
},
request: {
AccountName: valuesAccountName.input,
Username: valuesUserName.input,
Password: base64PW,
},
response: {
XAuthorization: '',
UserId: '',
WebSocketSessionId: '',
},
})
.then(resp => {
if (
resp.response.XAuthorization !== '' &&
resp.response.UserId !== '' &&
resp.response.WebSocketSessionId !== ''
) {
let regPro = {
...store.getState().appVariables.preferences
.RegisterProcess,
};
regPro.isRegistering = false;
store.dispatch(appVarActions.setRegisterProcess(regPro));
MyUserManager.createNewMyUser(
resp.response.UserId,
valuesAccountName.input,
valuesUserName.input,
store.getState().appVariables.preferences.RegisterProcess
.EMail,
resp.response.XAuthorization,
resp.response.WebSocketSessionId,
);
navigation.popToTop();
navigation.goBack();
}
setLoading(false);
})
.catch(resp => {
let errorText =
lang.account.registration.stepFinal.registerError[
resp.status
] || 'Error ' + resp.status;
showToastNow(errorText);
setLoading(false);
});
}, 300);
}}>
{lang.account.registration.stepFinal.button}
</Button>
</Center>
</ScrollView>
);
}
function Login() {
const lang = useSelector((state: RootState) => state.appVariables.lang);
const regPro = useSelector(
(state: RootState) => state.appVariables.preferences.RegisterProcess,
);
const {colors} = useTheme();
const dispatch = useDispatch();
const toast = useToast();
const navigation = useNavigation<RootScreenNavigationProp>();
const initNoErrors = {
wrongFormat: false,
alreadyExists: false,
noEntered: false,
unknown: undefined,
};
const [errorsMail, setErrorsMail] = useState(initNoErrors);
const [errorsPassword, setErrorsPassword] = useState(initNoErrors);
const [showPassword, setShowPassword] = useState(false);
const isErrorMail =
errorsMail.wrongFormat || errorsMail.alreadyExists || errorsMail.noEntered;
const isErrorPassword = errorsPassword.noEntered;
const errorTextMail = () => {
if (errorsMail.wrongFormat) {
return lang.account.registration.stepOne.addressInvalid;
}
if (errorsMail.noEntered) {
return lang.account.registration.stepOne.noMailEntered;
}
if (errorsMail.unknown !== undefined) {
return errorsMail.unknown;
}
};
const errorTextPassword = () => {
if (errorsPassword.noEntered) {
return lang.account.registration.stepFinal.passwordError.required;
}
if (errorsPassword.unknown !== undefined) {
return errorsPassword.unknown;
}
};
const [isLoading, setLoading] = useState(false);
const [values, setValues] = useState({email: regPro.EMail, password: ''});
const onButtonPress = () => {
if (values.email === '') {
let err = {...errorsMail};
err.noEntered = true;
setErrorsMail({...err});
}
if (values.password === '') {
let err = {...errorsPassword};
err.noEntered = true;
setErrorsPassword({...err});
} else if (validateEmail(values.email)) {
setLoading(true);
if (values.password.length < passwordOptions.minLength) {
setTimeout(() => {
setLoading(false);
showToast(toast, {
title: lang.account.login.wrongEmPw,
variant: 'solid',
status: 'error',
description: undefined,
isClosable: true,
rest: {},
});
}, 1000);
return;
}
let base64PW = new Buffer(values.password).toString('base64');
makeRequest({
path: apiBackendRequest.LOGIN,
requestHeader: {},
request: {
Email: values.email,
Password: base64PW,
},
response: {
XAuthorization: '',
UserId: '',
WebSocketSessionId: '',
},
})
.then(resp => {
setLoading(false);
let accName = 'ga';
let userName = 'ga';
MyUserManager.createNewMyUser(
resp.response.UserId,
accName,
userName,
values.email,
resp.response.XAuthorization,
resp.response.WebSocketSessionId,
)
.then(() => {
showToast(toast, {
title: lang.account.login.success,
variant: 'solid',
status: 'success',
description: undefined,
isClosable: true,
rest: {},
});
setLoading(false);
navigation.goBack();
})
.catch(() => {
setLoading(false);
showToast(toast, {
title: lang.account.login.failed,
variant: 'solid',
status: 'error',
description: undefined,
isClosable: true,
rest: {},
});
});
})
.catch(resp => {
setLoading(false);
if (resp.status === 422) {
showToast(toast, {
title: lang.account.login.wrongEmPw,
variant: 'solid',
status: 'error',
description: undefined,
isClosable: true,
rest: {},
});
} else {
showToast(toast, {
title: lang.error + ' ' + resp.status,
variant: 'solid',
status: 'error',
description: undefined,
isClosable: true,
rest: {},
});
}
});
} else {
let err = {...errorsMail};
err.wrongFormat = true;
setErrorsMail({...err});
}
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<Box alignItems="center" mt={5}>
<FormControl
w="75%"
maxW="350px"
isRequired
isDisabled={isLoading}
isInvalid={isErrorMail}>
<FormControl.Label>
{lang.account.registration.stepOne.title}
</FormControl.Label>
<Input
autoFocus
autoComplete="email"
placeholder={lang.account.registration.stepOne.title}
value={values.email}
onSubmitEditing={onButtonPress}
autoCapitalize={'none'}
maxLength={emailOptions.maxLength}
keyboardType={'email-address'}
onChangeText={text => {
const mail = text.replaceAll(' ', '');
let val = {...values};
val.email = mail;
setValues(val);
if (errorsMail.noEntered && mail !== '') {
let err = errorsMail;
err.noEntered = false;
setErrorsMail({...err});
}
if (errorsMail.wrongFormat && validateEmail(mail)) {
let err = errorsMail;
err.wrongFormat = false;
setErrorsMail({...err});
}
}}
/>
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
{errorTextMail()}
</FormControl.ErrorMessage>
</FormControl>
<FormControl
marginBottom={5}
w="75%"
maxW="350px"
isRequired
isDisabled={isLoading}
isInvalid={isErrorPassword}>
<FormControl.Label>
{lang.account.registration.stepFinal.password}
</FormControl.Label>
<Input
autoFocus
autoComplete="password"
placeholder={lang.account.registration.stepFinal.password}
value={values.password}
onSubmitEditing={onButtonPress}
autoCapitalize={'none'}
maxLength={emailOptions.maxLength}
type={showPassword === false ? 'password' : 'text'}
keyboardType={
showPassword === true ? 'visible-password' : undefined
}
InputRightElement={
<IconButton
mr="2"
focusable={false}
onPress={() => setShowPassword(!showPassword)}
icon={
<MaterialIcons
size={20}
color={colors.white[200]}
name={showPassword ? 'visibility' : 'visibility-off'}
/>
}
borderRadius="full"
/>
}
onChangeText={password => {
let val = {...values};
val.password = password;
setValues(val);
if (errorsPassword.noEntered && password !== '') {
let err = errorsPassword;
err.noEntered = false;
setErrorsMail({...err});
}
}}
/>
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
{errorTextPassword()}
</FormControl.ErrorMessage>
</FormControl>
<Button
w="75%"
maxW="350px"
colorScheme="primary"
rounded="xl"
_text={{fontSize: 'xl'}}
isLoading={isLoading}
onPress={onButtonPress}>
{lang.account.login.title}
</Button>
</Box>
</ScrollView>
);
}

View File

@ -1,5 +1,6 @@
import {EMail, XToken} from '@caj/helper/types';
import {EMail, UserId, XToken} from '@caj/configs/types';
import {VersionType} from '@caj/helper/version';
import { MyUserAccount } from '@caj/user/types';
import {APP_VERSION} from './appNonSaveVar';
export enum ThemeMode {
@ -54,6 +55,8 @@ export interface PREFERENCES_VARS {
version: VersionType;
theme: ThemeMode;
RegisterProcess: RegisterProcess;
selectedAccount: UserId | "none";
accounts: {[key: UserId]: MyUserAccount};
}
export const preferences_vars_default: PREFERENCES_VARS = {
@ -64,4 +67,6 @@ export const preferences_vars_default: PREFERENCES_VARS = {
XToken: undefined,
EMail: '',
},
selectedAccount: "none",
accounts: {},
};

View File

@ -10,6 +10,8 @@ import {
import {non_save_vars, NON_SAVE_VARS} from './appNonSaveVar';
import LangFormat from '@caj/lang/default';
import {lang as defaultLang} from '@caj/lang/en';
import { UserId } from './types';
import { MyUserAccount } from '@caj/user/types';
export interface appVariablesState {
preferences: PREFERENCES_VARS;
@ -37,6 +39,12 @@ export const appVariablesSlice = createSlice({
setRegisterProcess: (state, action: PayloadAction<RegisterProcess>) => {
state.preferences.RegisterProcess = action.payload;
},
setCurrentAccount:(state, action: PayloadAction<UserId>) => {
state.preferences.selectedAccount = action.payload;
},
setAccount:(state, action: PayloadAction<MyUserAccount>) => {
state.preferences.accounts[action.payload.UserId] = action.payload;
},
},
});

View File

@ -1,13 +1,19 @@
import {ThemeMode} from './appVar';
export type EMail = string;
export type langCode = 'en' | string;
export type timestamp = number;
export type AccountName = string;
export type Username = string;
export type Username = string | undefined;
export type Password = string;
export type UserAgent = string;
export type XToken = string;
export type verifyId = string;
export type SessionId = string;
export type XAuthorization = string;
export type UserId = string;
export type WebSocketSessionId = string;
@ -18,6 +24,7 @@ export const accountNameOptions = {
return text.match('^[a-zA-Z0-9_.]+$') !== null;
},
};
export const userNameOptions = {
minLength: 2,
maxLength: 24,
@ -29,8 +36,9 @@ export const userNameOptions = {
export const passwordOptions = {
minLength: 6,
maxLength: 64,
minBits: 50,
isAllowed: (text: string): boolean => {
return true;
return /\W/.test(text) && /[a-zA-Z]/.test(text) && /[a-zA-Z]/.test(text);
},
};

View File

@ -0,0 +1,31 @@
import {createSlice} from '@reduxjs/toolkit';
import type {PayloadAction} from '@reduxjs/toolkit';
import LangFormat from '@caj/lang/default';
import {lang as defaultLang} from '@caj/lang/en';
import {UserId} from './types';
import {MyUserAccount} from '@caj/user/types';
export interface userList {
myUserList: {[key: UserId]: MyUserAccount};
}
const initialState: userList = {
myUserList: {},
};
export const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
setAccount: (state, action: PayloadAction<MyUserAccount>) => {
state.myUserList[action.payload.UserId] = action.payload;
},
},
});
// Action creators are generated for each case reducer function
const {actions} = usersSlice;
export const usersActions = actions;
export default usersSlice.reducer;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,638 @@
import * as PopularPasswords from './PopularPasswords.js';
const PatternID = {
LowerAlpha: 'L',
UpperAlpha: 'U',
Digit: 'D',
Special: 'S',
High: 'H',
Other: 'X',
Dictionary: 'W',
Repetition: 'R',
Number: 'N',
DiffSeq: 'C',
All: "LUDSHXWRNC"
};
const PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
const UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const LowerCase = "abcdefghijklmnopqrstuvwxyz";
const Digits = "0123456789";
const HighAnsiChars = (() => {
let sbHighAnsi = [], ch;
for(ch = 0x00A1; ch <= 0x00AC; ++ch)
sbHighAnsi.push(ch);
for(ch = 0x00AE; ch < 0x00FF; ++ch)
sbHighAnsi.push(ch);
sbHighAnsi.push(0x00FF);
return String.fromCharCode.apply(null, sbHighAnsi);
})();
function Assert(ok, msg) {
if (!ok) {
throw new Error(msg);
}
}
class QeCharType {
constructor(chTypeID, strAlphabet, bIsConsecutive) {
let nChars;
if (typeof strAlphabet === 'string') {
if(strAlphabet.length === 0) throw new Error();
} else if (typeof strAlphabet === 'number') {
if(nChars <= 0) throw new RangeError();
nChars = strAlphabet;
strAlphabet = null;
bIsConsecutive = false;
} else {
throw new Error();
}
this.TypeID = chTypeID;
this.Alphabet = strAlphabet;
this.CharCount = nChars || this.Alphabet.length;
this.m_chFirst = (bIsConsecutive ? this.Alphabet.charCodeAt(0) : null);
this.m_chLast = (bIsConsecutive ? this.Alphabet.charCodeAt(this.CharCount - 1) : null);
this.CharSize = Math.log2(this.CharCount);
nChars || Assert((this.m_chLast - this.m_chFirst) == (this.CharCount - 1) || !bIsConsecutive);
}
Contains(ch)
{
if(this.m_chLast !== null)
return ((ch >= this.m_chFirst) && (ch <= this.m_chLast));
if (this.Alphabet.length === 0) throw new Error('Don\'t call for catch-none set')
return (this.Alphabet.indexOf(String.fromCharCode(ch)) >= 0);
}
}
class EntropyEncoder {
constructor(strAlphabet, uBaseWeight,
uCharWeight, uOccExclThreshold)
{
if(strAlphabet === null || strAlphabet.length === 0) throw new Error();
this.m_strAlph = strAlphabet;
this.m_uBaseWeight = uBaseWeight;
this.m_uCharWeight = uCharWeight;
this.m_uOccExclThreshold = uOccExclThreshold;
this.m_dHisto = {}
}
Reset()
{
this.m_dHisto = {};
}
Write(ch)
{
Assert(this.m_strAlph.indexOf(ch) >= 0);
let uOcc = this.m_dHisto[ch] || 0;
Assert(ch in this.m_dHisto || uOcc === 0);
this.m_dHisto[ch] = uOcc + 1;
}
GetOutputSize()
{
let uTotalWeight = this.m_uBaseWeight * this.m_strAlph.length;
for (let u of Object.values(this.m_dHisto))
{
Assert(u >= 1);
if(u > this.m_uOccExclThreshold)
uTotalWeight += (u - this.m_uOccExclThreshold) * this.m_uCharWeight;
}
let dSize = 0.0, dTotalWeight = uTotalWeight;
for (let u of Object.values(this.m_dHisto))
{
let uWeight = this.m_uBaseWeight;
if(u > this.m_uOccExclThreshold)
uWeight += (u - this.m_uOccExclThreshold) * this.m_uCharWeight;
dSize -= u * Math.log2(uWeight / dTotalWeight);
}
return dSize;
}
}
class MultiEntropyEncoder {
constructor() {
this.m_dEncs = {};
}
AddEncoder(chTypeID, ec)
{
Assert(ec);
Assert(!(chTypeID in this.m_dEncs));
this.m_dEncs[chTypeID] = ec;
}
Reset()
{
for(let ec of Object.values(this.m_dEncs)) {
ec.Reset();
}
}
Write(chTypeID, chData)
{
let ec = this.m_dEncs[chTypeID];
if(!ec)
return false;
ec.Write(chData);
return true;
}
GetOutputSize()
{
let d = 0.0;
for (let ec of Object.values(this.m_dEncs))
{
d += ec.GetOutputSize();
}
return d;
}
}
class QePatternInstance
{
constructor(iPosition, nLength, chPatternID, dblCost) {
let ctSingle;
if (typeof dblCost === 'number') {
this.Position = iPosition;
this.Length = nLength;
this.PatternID = chPatternID;
this.Cost = dblCost;
this.SingleCharType = null;
} else {
ctSingle = chPatternID;
this.Position = iPosition;
this.Length = nLength;
this.PatternID = ctSingle.TypeID;
this.Cost = ctSingle.CharSize;
this.SingleCharType = ctSingle;
}
}
}
class QePathState {
constructor(iPosition, lPath) {
this.Position = iPosition;
this.Path = lPath;
}
}
let m_objSyncInit;
let m_lCharTypes;
function EnsureInitialized() {
if(m_lCharTypes == null)
{
let strSpecial = PrintableAsciiSpecial;
strSpecial = strSpecial + " ";
let nSp = strSpecial.length;
let nHi = HighAnsiChars.length;
m_lCharTypes = [];
m_lCharTypes.push(new QeCharType(PatternID.LowerAlpha,
LowerCase, true));
m_lCharTypes.push(new QeCharType(PatternID.UpperAlpha,
UpperCase, true));
m_lCharTypes.push(new QeCharType(PatternID.Digit,
Digits, true));
m_lCharTypes.push(new QeCharType(PatternID.Special,
strSpecial, false));
m_lCharTypes.push(new QeCharType(PatternID.High,
HighAnsiChars, false));
m_lCharTypes.push(new QeCharType(PatternID.Other,
0x10000 - (2 * 26) - 10 - nSp - nHi));
}
}
function GetCharType(ch) {
let nTypes = m_lCharTypes.length;
Assert((nTypes > 0) && (m_lCharTypes[nTypes - 1].CharCount > 256));
for(let i = 0; i < (nTypes - 1); ++i)
{
if(m_lCharTypes[i].Contains(ch))
return m_lCharTypes[i];
}
return m_lCharTypes[nTypes - 1];
}
function ComputePathCost(l, vPassword, ecPattern, mcData)
{
ecPattern.Reset();
for(let i = 0; i < l.length; ++i)
ecPattern.Write(l[i].PatternID);
let dblPatternCost = ecPattern.GetOutputSize();
mcData.Reset();
let dblDataCost = 0.0;
for(let pi of l)
{
let tChar = pi.SingleCharType;
if(tChar != null)
{
let ch = vPassword[pi.Position];
if(!mcData.Write(tChar.TypeID, ch))
dblDataCost += pi.Cost;
}
else dblDataCost += pi.Cost;
}
dblDataCost += mcData.GetOutputSize();
return (dblPatternCost + dblDataCost);
}
function FindRepetitions(vPassword, vPatterns)
{
let v = stringToArray(vPassword);
let n = vPassword.length;
let chErased = 0xffff;
for(let m = (n / 2); m >= 3; --m)
{
for(let x1 = 0; x1 <= (n - (2 * m)); ++x1)
{
let bFoundRep = false;
for(let x2 = (x1 + m); x2 <= (n - m); ++x2)
{
if(PartsEqual(v, x1, x2, m))
{
let dblCost = Math.log2(x1 + 1) + Math.log2(m);
vPatterns[x2].push(new QePatternInstance(x2, m,
PatternID.Repetition, dblCost));
chErased = ErasePart(v, x2, m, chErased);
bFoundRep = true;
}
}
if (bFoundRep) chErased = ErasePart(v, x1, m, chErased);
}
}
}
function PartsEqual(v, x1, x2, nLength)
{
for(let i = 0; i < nLength; ++i)
{
if(v[x1 + i] != v[x2 + i]) return false;
}
return true;
}
function ErasePart(v, i, n, chErased)
{
for(let j = 0; j < n; ++j) {
v[i + j] = chErased;
--chErased;
}
return chErased;
}
function stringToArray(string) {
let array = [];
for (let i = 0; i < string.length; i++) {
array.push(string.charCodeAt(i));
}
return array;
}
function FindNumbers(vPassword, vPatterns)
{
let n = vPassword.length;
let sb = [];
for(let i = 0; i < n; ++i)
{
let ch = vPassword.charCodeAt(i);
if((ch >= 0x30) && (ch <= 0x39)) sb.push(ch);
else
{
AddNumberPattern(vPatterns, sb, i - sb.length);
sb = [];
}
}
AddNumberPattern(vPatterns, sb, n - sb.length);
}
function AddNumberPattern(vPatterns, sb, i)
{
if(sb.length <= 2) return;
let strNumber = String.fromCharCode.apply(null,sb);
let nZeros = 0;
for(let j = 0; j < strNumber.length; ++j)
{
if(strNumber.charCodeAt(j) != 0x30) break;
++nZeros;
}
let dblCost = Math.log2(nZeros + 1);
if(nZeros < strNumber.length)
{
let strNonZero = strNumber.substring(nZeros);
dblCost += Math.log2(parseFloat(strNonZero));
}
vPatterns[i].push(new QePatternInstance(i, strNumber.length,
PatternID.Number, dblCost));
}
function FindDiffSeqs(vPassword, vPatterns) {
let n = vPassword.length;
let d = Infinity, p = 0;
for(let i = 1; i <= n; ++i)
{
let dCur = ((i == n) ? Infinity :
(vPassword.charCodeAt(i) - vPassword.charCodeAt(i - 1)));
if(dCur != d)
{
if((i - p) >= 3) // At least 3 chars involved
{
let ct = GetCharType(vPassword.charCodeAt(p));
let dblCost = ct.CharSize + Math.log2(i - p - 1);
vPatterns[p].push(new QePatternInstance(p,
i - p, PatternID.DiffSeq, dblCost));
}
d = dCur;
p = i - 1;
}
}
}
function DecodeLeet(str) {
let newstr = '';
for (let i = 0; i < str.length; i++) {
let char = str.charAt(i);
let decoded = DecodeLeetChar(char);
newstr += decoded;
}
return newstr;
}
function FindPopularPasswords(vPassword, vPatterns) {
let n = vPassword.length;
let vLower = vPassword.toLowerCase();
let vLeet = DecodeLeet(vLower);
for(let nSubLen = Math.min(n, PopularPasswords.getMaxLength()); nSubLen >= 3; --nSubLen) {
if (!PopularPasswords.ContainsLength(nSubLen)) continue;
for(let i = 0; i <= (n - nSubLen); ++i)
{
let vSub = vLower.substring(i, i + nSubLen);
if (!vSub || vSub.indexOf('\u0000') !== -1) {
continue;
}
if(!EvalAddPopularPasswordPattern(vPatterns, vPassword,
i, vSub, 0.0))
{
let vLeetSub = vLeet.substring(i, nSubLen);
if(EvalAddPopularPasswordPattern(vPatterns, vPassword,
i, vLeetSub, 1.5))
{
vLower = StringClear(vLower, i, nSubLen); // Not vLeet
}
}
else
{
vLower = StringClear(vLower, i, nSubLen); // Not vLeet
}
}
}
}
function StringClear(str, pos, count) {
let erased = ''
for (let i = 0;i < count;i++) {
erased += '\u0000'
}
return str.substring(0, pos) + erased + str.substring(pos + count);
}
function DecodeLeetChar(chLeet) {
if((chLeet.charCodeAt(0) >= 0x00C0) && (chLeet.charCodeAt(0) <= 0x00C6)) return 'a';
if((chLeet.charCodeAt(0) >= 0x00C8) && (chLeet.charCodeAt(0) <= 0x00CB)) return 'e';
if((chLeet.charCodeAt(0) >= 0x00CC) && (chLeet.charCodeAt(0) <= 0x00CF)) return 'i';
if((chLeet.charCodeAt(0) >= 0x00D2) && (chLeet.charCodeAt(0) <= 0x00D6)) return 'o';
if((chLeet.charCodeAt(0) >= 0x00D9) && (chLeet.charCodeAt(0) <= 0x00DC)) return 'u';
if((chLeet.charCodeAt(0) >= 0x00E0) && (chLeet.charCodeAt(0) <= 0x00E6)) return 'a';
if((chLeet.charCodeAt(0) >= 0x00E8) && (chLeet.charCodeAt(0) <= 0x00EB)) return 'e';
if((chLeet.charCodeAt(0) >= 0x00EC) && (chLeet.charCodeAt(0) <= 0x00EF)) return 'i';
if((chLeet.charCodeAt(0) >= 0x00F2) && (chLeet.charCodeAt(0) <= 0x00F6)) return 'o';
if((chLeet.charCodeAt(0) >= 0x00F9) && (chLeet.charCodeAt(0) <= 0x00FC)) return 'u';
switch(chLeet)
{
case '4':
case '@':
case '?':
case '^':
case '\u00AA': return 'a';
case '8':
case '\u00DF': return 'b';
case '(':
case '{':
case '[':
case '<':
case '\u00A2':
case '\u00A9':
case '\u00C7':
case '\u00E7': return 'c';
case '\u00D0':
case '\u00F0': return 'd';
case '3':
case '\u20AC':
case '&':
case '\u00A3': return 'e';
case '6':
case '9': return 'g';
case '#': return 'h';
case '1':
case '!':
case '|':
case '\u00A1':
case '\u00A6': return 'i';
case '\u00D1':
case '\u00F1': return 'n';
case '0':
case '*':
case '\u00A4': // Currency
case '\u00B0': // Degree
case '\u00D8':
case '\u00F8': return 'o';
case '\u00AE': return 'r';
case '$':
case '5':
case '\u00A7': return 's';
case '+':
case '7': return 't';
case '\u00B5': return 'u';
case '%':
case '\u00D7': return 'x';
case '\u00A5':
case '\u00DD':
case '\u00FD':
case '\u00FF': return 'y';
case '2': return 'z';
default: return chLeet;
}
}
function EvalAddPopularPasswordPattern(vPatterns, vPassword, i, sub, dblCostPerMod)
{
let IsPopularPassword = PopularPasswords.IsPopularPassword(sub);
let uDictSize = PopularPasswords.GetDictSize(sub.length);
if(!IsPopularPassword)
return false;
let n = sub.length;
let d = HammingDist(sub, 0, vPassword, i, n);
let dblCost = Math.log2(uDictSize);
// dblCost += Math.log2(n binom d)
let k = Math.min(d, n - d);
for(let j = n; j > (n - k); --j)
dblCost += Math.log2(j);
for(let j = k; j >= 2; --j)
dblCost -= Math.log2(j);
dblCost += dblCostPerMod * d;
vPatterns[i].push(new QePatternInstance(i, n, PatternID.Dictionary,
dblCost));
return true;
}
function HammingDist(v1, iOffset1, v2, iOffset2, nLength)
{
let nDist = 0;
for(let i = 0; i < nLength; ++i)
{
if(v1.charCodeAt(iOffset1 + i) !== v2.charCodeAt(iOffset2 + i)) ++nDist;
}
return nDist;
}
export default function PasswordQualityCalculator(vPassword)
{
if (typeof vPassword !== 'string' || vPassword.length === 0) return 0;
EnsureInitialized();
let n = vPassword.length;
let vPatterns = [];
for (let i = 0; i < n; i++) {
vPatterns[i] = [
new QePatternInstance(i, 1, GetCharType(vPassword.charCodeAt(i)))
];
}
FindRepetitions(vPassword, vPatterns);
FindNumbers(vPassword, vPatterns);
FindDiffSeqs(vPassword, vPatterns);
FindPopularPasswords(vPassword, vPatterns);
// Encoders must not be static, because the entropy estimation
// may run concurrently in multiple threads and the encoders are
// not read-only
let ecPattern = new EntropyEncoder(PatternID.All, 0, 1, 0);
let mcData = new MultiEntropyEncoder();
for(let i = 0; i < (m_lCharTypes.length - 1); ++i)
{
// Let m be the alphabet size. In order to ensure that two same
// characters cost at least as much as a single character, for
// the probability p and weight w of the character it must hold:
// -log(1/m) >= -2*log(p)
// <=> log(1/m) <= log(p^2) <=> 1/m <= p^2 <=> p >= sqrt(1/m);
// sqrt(1/m) = (1+w)/(m+w)
// <=> m+w = (1+w)*sqrt(m) <=> m+w = sqrt(m) + w*sqrt(m)
// <=> w*(1-sqrt(m)) = sqrt(m) - m <=> w = (sqrt(m)-m)/(1-sqrt(m))
// <=> w = (sqrt(m)-m)*(1+sqrt(m))/(1-m)
// <=> w = (sqrt(m)-m+m-m*sqrt(m))/(1-m) <=> w = sqrt(m)
let uw = Math.sqrt(m_lCharTypes[i].CharCount) | 0;
mcData.AddEncoder(m_lCharTypes[i].TypeID, new EntropyEncoder(
m_lCharTypes[i].Alphabet, 1, uw, 1));
}
let dblMinCost = Infinity;
let tStart = Date.now();
let sRec = [];
sRec.push(new QePathState(0, []));
while(sRec.length > 0)
{
let tDiff = Date.now() - tStart;
if(tDiff > 500) break;
let s = sRec.pop();
if(s.Position >= n)
{
Assert(s.Position === n);
let dblCost = ComputePathCost(s.Path, vPassword,
ecPattern, mcData);
if(dblCost < dblMinCost) dblMinCost = dblCost;
}
else
{
let lSubs = vPatterns[s.Position];
for(let i = lSubs.length - 1; i >= 0; --i)
{
let pi = lSubs[i];
Assert(pi.Position == s.Position);
Assert(pi.Length >= 1);
let lNewPath = [];
lNewPath.push(...s.Path);
lNewPath.push(pi);
let sNew = new QePathState(s.Position +
pi.Length, lNewPath);
sRec.push(sNew);
}
}
}
return Math.ceil(dblMinCost);
}
PasswordQualityCalculator.PopularPasswords = PopularPasswords;

View File

@ -0,0 +1,47 @@
const m_dicts = {};
export function getMaxLength() {
let iMaxLen = 0;
for (let iLen of Object.keys(m_dicts)) {
if (parseInt(iLen) > iMaxLen) iMaxLen = parseInt(iLen);
}
return iMaxLen;
}
export function ContainsLength(nLength) {
return nLength in m_dicts;
}
export function IsPopularPassword(password) {
if (password == null) throw new Error();
if (password.length == 0) { return false; }
if (!(password.length in m_dicts)) {
return false;
}
return m_dicts[password.length].includes(password);
}
export function GetDictSize(length) {
if (!(length in m_dicts)) {
return 0;
}
return m_dicts[length].length;
}
export function load(passwordList) {
for (let pw of passwordList) {
if (pw.length in m_dicts) {
m_dicts[pw.length].push(pw);
} else {
m_dicts[pw.length] = [pw];
}
}
}
export function reset() {
m_dicts = {}
}

View File

@ -10,13 +10,14 @@ import {
AccountName,
EMail,
Password,
SessionId,
XAuthorization,
UserId,
Username,
verifyId,
WebSocketSessionId,
XToken,
} from './types';
} from '../configs/types';
import MyUserManager from '@caj/user/MyUserManager';
export const apiPath = {
backend: {
@ -30,19 +31,24 @@ export enum apiBackendRequest {
REGISTER_STEP_2 = '/verify/email/:xToken/:verifyId',
REGISTER_STEP_FINAL = '/admin/users',
REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK = '/admin/users/validation/accountname',
LOGIN = '/user',
GET_USER_PROFILE = '/users/:userId',
APP_START = '/appstart',
}
type requestGET = {[key: string]: string};
type requestHeader = {[key: string]: string | undefined};
export interface defaultRequest {
status?: 200 | 400 | 422 | 500;
error?: string;
requestGET?: requestGET;
requestHeader?: requestHeader;
}
interface REGISTER_STEP_1 extends defaultRequest {
path: apiBackendRequest.REGISTER_STEP_1;
requestHeader: {};
request: {
Email: EMail;
};
@ -52,18 +58,20 @@ interface REGISTER_STEP_1 extends defaultRequest {
}
interface REGISTER_RESEND_MAIL extends defaultRequest {
path: apiBackendRequest.REGISTER_RESEND_MAIL;
requestHeader: {};
requestHeader: {
'X-Token': XToken | undefined;
};
request: {
Email: EMail;
};
// response: {
// XToken: XToken | undefined;
//};
response: {
XToken: XToken | undefined;
};
}
interface REGISTER_STEP_2 extends defaultRequest {
path: apiBackendRequest.REGISTER_STEP_2;
requestHeader: {};
requestGET: {
':xToken': XToken;
':verifyId': verifyId;
@ -84,7 +92,7 @@ interface REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK extends defaultRequest {
interface REGISTER_STEP_FINAL extends defaultRequest {
path: apiBackendRequest.REGISTER_STEP_FINAL;
requestHeader: {
XToken: XToken;
'X-Token': XToken;
};
request: {
AccountName: AccountName;
@ -92,18 +100,70 @@ interface REGISTER_STEP_FINAL extends defaultRequest {
Password: Password;
};
response?: {
SessionId: SessionId;
XAuthorization: XAuthorization;
UserId: UserId;
WebSocketSessionId: WebSocketSessionId;
};
}
interface LOGIN extends defaultRequest {
path: apiBackendRequest.LOGIN;
request: {
Email: EMail;
Password: Password;
};
response?: {
XAuthorization: XAuthorization;
UserId: UserId;
WebSocketSessionId: WebSocketSessionId;
};
}
interface GET_USER_PROFILE extends defaultRequest {
path: apiBackendRequest.GET_USER_PROFILE;
requestGET: {
':userId': UserId;
};
response: {
AccountName: AccountName;
Username: Username;
Description: string;
FollowersCount: number;
FollowingCount: number;
XpLevel: number;
XpPoints: number;
AvatarUrl: string;
};
}
interface APP_START extends defaultRequest {
path: apiBackendRequest.APP_START;
response: {
TokenValid: boolean;
AccountName: AccountName;
Username: Username;
Description: string;
FollowersCount: number;
FollowingCount: number;
XpLevel: number;
XpPoints: number;
AccountStatus: number;
AvatarUrl: string;
Events: any;
};
}
type FetchTypes =
| REGISTER_STEP_1
| REGISTER_RESEND_MAIL
| REGISTER_STEP_2
| REGISTER_STEP_FINAL
| REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK;
| REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK
| LOGIN
| GET_USER_PROFILE
| APP_START;
function isA(obj: any): obj is REGISTER_STEP_1 {
return obj.request !== undefined;
@ -130,9 +190,20 @@ export function makeRequest<T1 extends FetchTypes>(type: T1): Promise<T1> {
'X-Language': store.getState().appVariables.lang.details.langCode,
};
if (type.requestHeader !== undefined) {
for (let key in type.requestHeader) {
let val = (type.requestHeader as requestHeader)[key];
if (val !== undefined) headers[key] = val;
}
}
if (Platform.OS === 'android' || Platform.OS === 'ios')
headers['User-Agent'] = getUserAgent();
const SessionId = MyUserManager.getSessionId();
if (SessionId !== undefined) headers['X-Authorization'] = SessionId;
const requestOptions: RequestInit = {
method: makeRequestObj.request !== undefined ? 'POST' : 'GET',

View File

@ -0,0 +1,4 @@
import {initDatabase} from './bdm/init';
const BigDataManager = {initDatabase};
export default BigDataManager;

View File

@ -0,0 +1,18 @@
import Realm from 'realm';
import DBSchemas from './schemas';
export const initDatabase = (): Promise<void> => {
return new Promise((resolve, reject) => {
setTimeout(() => {
for (const key in DBSchemas) {
const Schema = DBSchemas[key as keyof typeof DBSchemas];
Realm.open({
schema: [Schema.details],
schemaVersion: Schema.version,
path: Schema.filePath,
});
}
resolve();
}, 1000);
});
};

View File

@ -0,0 +1,16 @@
import {openDB, deleteDB, wrap, unwrap} from 'idb';
import DBSchemas from './schemas';
export const initDatabase = (): Promise<void> => {
return new Promise(async (resolve, reject) => {
setTimeout(async () => {
for (const key in DBSchemas) {
const Schema = DBSchemas[key as keyof typeof DBSchemas];
await openDB(Schema.details.name, Schema.version, {
upgrade: Schema.migration,
});
}
resolve();
}, 1000);
});
};

View File

@ -0,0 +1,15 @@
import {MigrationCallback} from 'realm';
export const usersDBMigration: MigrationCallback = (oldRealm, newRealm) => {
/*// only apply this change if upgrading to schemaVersion 2
if (oldRealm.schemaVersion < 2) {
const oldObjects = oldRealm.objects('Person');
const newObjects = newRealm.objects('Person');
// loop through all objects and set the fullName property in the new schema
for (const objectIndex in oldObjects) {
const oldObject = oldObjects[objectIndex];
const newObject = newObjects[objectIndex];
newObject.fullName = `${oldObject.firstName} ${oldObject.lastName}`;
}
}*/
};

View File

@ -0,0 +1,68 @@
import {IDBPDatabase, IDBPTransaction, StoreNames} from 'idb';
import DBSchemas from './schemas';
interface migrationArguments {
db: IDBPDatabase<unknown>;
oldVersion: number;
newVersion: number | null;
transaction: IDBPTransaction<unknown, StoreNames<unknown>[], 'versionchange'>;
event: IDBVersionChangeEvent;
}
type upgradeFunc = (
database: IDBPDatabase<unknown>,
oldVersion: number,
newVersion: number | null,
transaction: IDBPTransaction<unknown, StoreNames<unknown>[], 'versionchange'>,
event: IDBVersionChangeEvent,
) => void;
export const usersDBMigration: upgradeFunc = (
db,
oldVersion,
newVersion,
transaction,
event,
) => {
const Schema = DBSchemas['users'];
if (oldVersion == 0) {
// perform the initialization
db.createObjectStore(Schema.details.name, {
keyPath: Schema.details.primaryKey,
});
} else if (newVersion !== null) {
let ver = oldVersion;
while (ver < newVersion) {
console.log('upgrade from v', ver, ' to v', ver + 1);
ver++;
}
}
};
export const chatDBMigration: upgradeFunc = (
db,
oldVersion,
newVersion,
transaction,
event,
) => {
const Schema = DBSchemas['chat'];
if (oldVersion == 0) {
// perform the initialization
db.createObjectStore(Schema.details.name, {
keyPath: Schema.details.primaryKey,
});
} else if (newVersion !== null) {
let ver = oldVersion;
while (ver < newVersion) {
console.log('upgrade from v', ver, ' to v', ver + 1);
ver++;
}
}
};

View File

@ -0,0 +1,47 @@
import {usersDBMigration} from './migration';
import {chatDBMigration} from './migration.web';
type databaseNames = 'users' | 'chat';
interface databaseConf {
filePath: string;
version: number;
migration?: any;
details: {
name: databaseNames;
properties: any;
primaryKey: string;
};
}
const users: databaseConf = {
filePath: 'users',
version: 1,
migration: usersDBMigration,
details: {
name: 'users',
properties: {
UserId: 'string',
AccountName: 'string',
Username: 'string',
},
primaryKey: 'UserId',
},
};
const chat: databaseConf = {
filePath: 'chat',
version: 1,
migration: chatDBMigration,
details: {
name: 'chat',
properties: {
UserId: 'string',
msg: 'string',
},
primaryKey: 'UserId',
},
};
const DBSchemas = {users, chat};
export default DBSchemas;

View File

@ -22,8 +22,15 @@ export default interface LangFormat {
error: string;
success: string;
account: {
login: {
title: string;
wrongEmPw: string;
failed: string;
success: string;
};
registration: {
registration: string;
info: string;
privacyPolicy: string;
termsOfUse: string;
@ -55,7 +62,11 @@ export default interface LangFormat {
accountNameError: {[key: string]: string};
userNameError: {[key: string]: string};
passwordError: {[key: string]: string};
passwordQuality: string;
passwordQualityList: {[key: string]: string};
noAllFieldsEntered: string;
button: string;
registerError: {[key: string]: string};
};
};
};

View File

@ -21,13 +21,20 @@ export const lang: LangFormat = {
error: 'Error',
success: 'Success',
account: {
login: {
title: 'Log in',
wrongEmPw: 'Wrong E-Mail or password',
failed: 'Login failed. Try again or contact support.',
success: 'Successfully logged in!',
},
registration: {
registration: 'Registration',
info: 'By registering, you agree to our ${TermsOfUse}. You can find out how we collect and use your data in our ${privacyPolicy}.',
privacyPolicy: 'privacy policy',
termsOfUse: 'Terms of Use',
stepOne: {
title: 'Enter E-Mail',
title: 'E-Mail',
success: 'A verification has sent to your E-Mail!',
addressExists: 'The E-Mail you entered is already in use.',
addressInvalid: 'The address you entered has an invalid format.',
@ -44,7 +51,7 @@ export const lang: LangFormat = {
'E-Mail verification has resent',
],
resendError: {
400: 'The E-Mail is already verified!',
400: 'Email is already in the registration process by another user!',
401: 'Your device have changed. Please use another E-Mail address.',
429: 'Too many requests in a too small period of time in a row',
},
@ -63,29 +70,44 @@ export const lang: LangFormat = {
accountName: 'AccountName',
password: 'Password',
passwordRepeat: 'Repeat password',
displayName: 'Other users see you like this:',
displayName: 'Other users will see you like this:',
accountNameError: {
tooLong: 'Too long. Max length are $(maxLength) character.',
tooShort: 'Too short. Min length are $(minLength) character.',
tooLong: 'Too long. Max length are ${maxLength} character.',
tooShort: 'Too short. Min length are ${minLength} character.',
required: 'This field is required',
invalid:
'Account names can only contain letters, numbers, underscores (_) and dots (.)',
exists: 'The account name you entered already exists.',
422: 'This name is already in use :(',
},
userNameError: {
tooLong: 'Too long. Max length are $(maxLength) character.',
tooShort: 'Too short. Min length are $(minLength) character.',
tooLong: 'Too long. Max length are ${maxLength} character.',
tooShort: 'Too short. Min length are ${minLength} character.',
required: 'This field is required',
},
passwordError: {
noMatch: 'Passwords do not match',
tooLong: 'Too long. Max length are $(maxLength) character.',
tooShort: 'Too short. Min length are $(minLength) character.',
tooLong: 'Too long. Max length are ${maxLength} character.',
tooShort: 'Too short. Min length are ${minLength} character.',
required: 'This field is required',
weak: 'Password is too weak',
invalid:
"Passwords must have at least one letter, one number, and one special character. It's for your security. :)",
},
passwordQuality: 'Password quality: ${quality}',
passwordQualityList: {
0: 'Very weak',
1: 'Weak',
2: 'Moderate',
3: 'Strong',
4: 'Very strong',
},
noAllFieldsEntered: 'Please fill all fields',
button: 'Finish registration',
registerError: {
400: 'Sign up process expired please try again',
401: 'User agent and/or IP has changed',
},
},
},
},

View File

@ -1,7 +1,7 @@
import appNonSaveVarReducer from '@caj/configs/appNonSaveVarReducer';
import {configureStore} from '@reduxjs/toolkit';
import appVariablesReducer from '../configs/appVarReducer';
import appVariablesReducer from '@caj/configs/appVarReducer';
export const store = configureStore({
reducer: {

View File

@ -0,0 +1,143 @@
import {appVarActions} from '@caj/configs/appVarReducer';
import {
AccountName,
EMail,
XAuthorization,
UserId,
Username,
WebSocketSessionId,
} from '@caj/configs/types';
import {saveVarChanges} from '@caj/helper/appData';
import {apiBackendRequest, makeRequest} from '@caj/helper/request';
import {store} from '@caj/redux/store';
import {MyUserAccount, createUserProp, SourceProp} from './types';
function createNewMyUser(
UserId: UserId,
AccountName: AccountName,
Username: Username,
EMail: EMail,
SessionId: XAuthorization,
WebSocketSessionId: WebSocketSessionId,
): Promise<void> {
return new Promise((resolve, reject) => {
let user: MyUserAccount = {
UserId,
AccountName: createUserProp(SourceProp.offline, AccountName),
Username: createUserProp(SourceProp.offline, Username),
Description: createUserProp(SourceProp.online),
FollowersCount: createUserProp(SourceProp.online),
FollowingCount: createUserProp(SourceProp.online),
XpLevel: createUserProp(SourceProp.online),
XpPoints: createUserProp(SourceProp.online),
EMail,
SessionId,
WebSocketSessionId,
lastUpdateTimestamp: createUserProp(
SourceProp.offline,
Math.floor(new Date().getTime() / 1000),
),
ProfilePicture: {
hq: createUserProp(SourceProp.offline),
lq: createUserProp(SourceProp.offline),
},
userSettings: {
lang: store.getState().appVariables.lang.details.langCode,
theme: store.getState().appVariables.preferences.theme,
},
};
console.log('SessionId', SessionId);
createMyUser(user);
makeRequest({
path: apiBackendRequest.APP_START,
requestGET: {':userId': UserId},
response: {
AccountName: '',
Description: '',
FollowersCount: 0,
FollowingCount: 0,
Username: '',
XpLevel: 0,
XpPoints: 0,
AvatarUrl: '',
AccountStatus: 0,
Events: {},
TokenValid: false,
},
})
.then(resp => {
let user = {...getMyUser(UserId)};
console.log(user);
user.AccountName = createUserProp(
SourceProp.offline,
resp.response.AccountName,
);
user.Username = createUserProp(
SourceProp.offline,
resp.response.Username,
);
user.Description = createUserProp(
SourceProp.offline,
resp.response.Description,
);
user.FollowersCount = createUserProp(
SourceProp.offline,
resp.response.FollowersCount,
);
user.FollowingCount = createUserProp(
SourceProp.offline,
resp.response.FollowingCount,
);
user.XpLevel = createUserProp(
SourceProp.offline,
resp.response.XpLevel,
);
user.XpPoints = createUserProp(
SourceProp.offline,
resp.response.XpPoints,
);
setMyUser(user);
resolve();
})
.catch(resp => {
console.error(resp.status);
reject();
});
});
}
function createMyUser(user: MyUserAccount) {
store.dispatch(appVarActions.setAccount(user));
store.dispatch(appVarActions.setCurrentAccount(user.UserId));
saveVarChanges();
}
function getMyUser(userId: UserId): MyUserAccount {
return store.getState().appVariables.preferences.accounts[userId];
}
function setMyUser(user: MyUserAccount) {
store.dispatch(appVarActions.setAccount(user));
saveVarChanges();
}
function getSessionId(userId?: UserId): XAuthorization | undefined {
const preferences = store.getState().appVariables.preferences;
let user = preferences.accounts[userId || preferences.selectedAccount];
if (user === undefined) return undefined;
let SessionId = user.SessionId;
console.log(userId || preferences.selectedAccount);
console.log(preferences.accounts[userId || preferences.selectedAccount]);
return SessionId;
}
const MyUserManager = {createNewMyUser, getSessionId};
export default MyUserManager;

View File

@ -0,0 +1,5 @@
import {UserId, XAuthorization} from '@caj/configs/types';
import {store} from '@caj/redux/store';
const UserManager = {};
export default UserManager;

62
src/caj/user/types.ts Normal file
View File

@ -0,0 +1,62 @@
import {ThemeMode} from '@caj/configs/appVar';
import {
AccountName,
EMail,
langCode,
XAuthorization,
timestamp,
UserId,
Username,
WebSocketSessionId,
} from '@caj/configs/types';
export enum SourceProp {
online = -1,
offline = 0,
cached = 1,
}
interface BasicUserProp<T1> {
source: SourceProp;
url?: string;
data?: T1;
}
export interface ProfilePicture {
lq: BasicUserProp<string>;
hq: BasicUserProp<string>;
}
export interface User {
UserId: UserId;
ProfilePicture: ProfilePicture;
lastUpdateTimestamp: BasicUserProp<timestamp>;
AccountName: BasicUserProp<AccountName>;
Username: BasicUserProp<Username>;
Description: BasicUserProp<string>;
FollowersCount: BasicUserProp<number>;
FollowingCount: BasicUserProp<number>;
XpLevel: BasicUserProp<number>;
XpPoints: BasicUserProp<number>;
}
export interface MyUserAccount extends User {
EMail: EMail;
SessionId: XAuthorization;
WebSocketSessionId: WebSocketSessionId;
userSettings: userSettings;
}
export interface userSettings {
theme: ThemeMode;
lang: langCode;
}
export function createUserProp<T1>(
source: SourceProp,
data?: T1,
url?: string,
): BasicUserProp<T1> {
return {source, data, url};
}

View File

@ -6,6 +6,7 @@
},
"target": "ESNext",
"allowSyntheticDefaultImports": true,
"allowJs": true,
"moduleResolution": "node",
"jsx": "react-native",
"strict": true,