Hello everyone, today in this final article of the series we will add data backup and recovery on Dropbox.
To set this up, we will need to create an application on Dropbox so we can use their API, and set up OAuth authentication so the user can authorize the application to connect to Dropbox.
Login screen
We will start by modifying the synchronization screen management so we can display a screen asking the user to log in to Dropbox before they can back up their data.
Start by creating a Synchronization folder in the Screens folder, then move the Synchronization.js file into it.
Then add an index.js file in the Synchronization folder with the following code:
/*
* @flow
*/
import React, { Component } from 'react';
import { View } from 'react-native';
import Synchronization from './Synchronization';
export default class Index extends Component {
render() {
return <Synchronization />;
}
}Next, replace the following imports in Synchronization.js:
import SettingRow from '../components/SettingRow';
import strings from '../locales/strings';
import { PlateformStyleSheet } from '../common/PlatformHelper';
import { IOS_BACKGROUND, WHITE, DELETE_COLOR, PRIMARY } from '../constants/colors';
import NavBar from '../components/NavBar';
par ceux-ci :
import SettingRow from '../../components/SettingRow';
import strings from '../../locales/strings';
import { PlateformStyleSheet } from '../../common/PlatformHelper';
import { IOS_BACKGROUND, WHITE, DELETE_COLOR, PRIMARY } from '../../constants/colors';
import NavBar from '../../components/NavBar';Now we will create the screen used to log in to Dropbox. Add Login.js, still in the Synchronization folder:
/*
* @flow
*/
import React, { Component } from 'react';
import { View, Image, Button, Text, StyleSheet } from 'react-native';
import strings from '../../locales/strings';
import { PRIMARY_TEXT, PRIMARY, WHITE } from '../../constants/colors';
import NavBar from '../../components/NavBar';
const img = require('../../img/paper_plane.png');
type Props = {
navigation: Object,
};
class Login extends Component<void, Props, void> {
render() {
return (
<View style={styles.container}>
<NavBar
title={strings.synchronization}
actionLeft={() => this.props.navigation.navigate('DrawerOpen')}
/>
<View style={styles.subContainer}>
<Image source={img} />
<Text style={styles.title}> {strings.synchInstruction}</Text>
<Button title={strings.login} onPress={()=> console.log("login"))} color={PRIMARY} />
</View>
</View>
);
}
}
export default Login;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: WHITE,
},
subContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 16,
},
title: {
fontWeight: 'bold',
marginTop: 32,
marginBottom: 32,
textAlign: 'center',
color: PRIMARY_TEXT,
},
});Reducers
To define which screen to display when the user goes to the synchronization section, we need to store information that lets us know whether the user has logged in or not.
To do this, we will add the synchronization reducer.
In the reducers folder, add the synchronization.js file with the following code:
/*
* @flow
*/
import type { Action } from '../actions/types';
import type { SynchronizationState } from './types';
const initialState: SynchronizationState = {
userLoggedToDropbox: false,
};
const synchronizationState = (
state: SynchronizationState = initialState,
action: Action
): SynchronizationState => {
switch (action.type) {
default:
return state;
}
};
export default synchronizationState;Next, add the SynchronizationState type in reducers/types.js:
...
export type SynchronizationState = {
+userLoggedToDropbox: boolean,
};
export type ReduxState = {
...
+synchronization: SynchronizationState,
};To finish, add the synchronization reducer to the root reducer:
...
import synchronization from './synchronization';
const rootReducer = combineReducers({
...
synchronization,
});Dropbox connection
Regarding Dropbox connection, I have already written an article about it. The behavior is the same for this application, so I invite you to follow the explanations in this article before continuing.
Make the following changes compared with the article above:
Replace the dropboxoauthsample custom scheme with vault wherever it appears in the code and Dropbox redirect URL configuration.
For iOS, replace #import "RCTLinkingManager.h" with #import <React/RCTLinkingManager.h>.
Let's move on to managing the Dropbox connection in code. This part is also covered in the article above, but here we will make it a bit more complex by adding Redux.
Modify synchronization/Login.js as follows:
/*
* @flow
*/
import React, { Component } from 'react';
import {
View,
Image,
Button,
Text,
StyleSheet,
Linking,
Platform,
} from 'react-native'; // #1
import uuidV4 from 'uuid/v4'; // #2
import strings from '../../locales/strings';
import { PRIMARY_TEXT, PRIMARY, WHITE } from '../../constants/colors';
import NavBar from '../../components/NavBar';
const appKey = 'YOUR_DROPBOX_APP_KEY'; // #3
const img = require('../../img/paper_plane.png');
type Props = {
navigation: Object,
};
type State = {
// #4
verification: string,
};
class Login extends Component<void, Props, State> {
state = {
verification: uuidV4(), // #5
};
logIn() {
// #6
const redirectUri =
Platform.OS === 'ios' ? 'vault://open' : 'https://www.example.com/open';
const url = `https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=${appKey}&redirect_uri=${redirectUri}&state=${this.state.verification}`;
Linking.openURL(url).catch((err) =>
console.error('An error occurred', err)
);
}
render() {
return (
<View style={styles.container}>
<NavBar
title={strings.synchronization}
actionLeft={() => this.props.navigation.navigate('DrawerOpen')}
/>
<View style={styles.subContainer}>
<Image source={img} />
<Text style={styles.title}> {strings.synchInstruction}</Text>
<Button
title={strings.login}
onPress={() => this.logIn()}
color={PRIMARY}
/>
</View>
</View>
);
}
}- We import Linking and Platform.
- We import uuidV4.
- We define the app key that Dropbox provides for our application.
- We define the State type with the verification variable (see the article above).
- We define the verification value.
- In the logIn method, we test the platform to define which redirectUri value will be passed in the URL, then build the URL and finally open the browser with Linking.
Now we will add the code that lets us subscribe to the Linking event corresponding to launching the application from an external URL.
To parse the parameters returned by Dropbox simply, we will add the shitty-qs library:
yarn add shitty-qsThen add the following code in Login.js:
componentDidMount() {
Linking.addEventListener('url', event => this.handleLinkingUrl(event));
}
componentWillUnmount() {
Linking.removeEventListener('url', event => this.handleLinkingUrl(event));
}
handleLinkingUrl(event: Object) {
const [, queryString] = event.url.match(/\#(.*)/);
const query = shittyQs(queryString);
if (this.state.verification === query.state) {
this.props.setAccessToken(query.access_token);
} else {
alert('Verification Failed');
}
}Here we subscribe when the component mounts and define the handleLinkingUrl callback; when the component unmounts, we unsubscribe.
In the handleLinkingUrl method, we retrieve the URL parameters, then test that the verification value passed from Dropbox is identical to the value in state. If so, we execute the SetAccessToken action. The SetAccessToken action is passed as a prop, so we need to add it to the Props type:
type Props = {
navigation: Object,
setAccessToken: (token: string) => void,
};To finish the connection part, we will add the SetAccessToken action and modify the Synchronization reducer.
In the actions folder, add the synchronization file with the following code:
/*
* @flow
*/
import type { ThunkAction, Dispatch, SetAccessTokenAction } from './types';
/*
*** Actions ***
*/
export const SetAccessToken = (token: string): ThunkAction => (dispatch: Dispatch) => {
dispatch(setAccessToken(token));
};
/*
*** Actions Creator ***
*/
const setAccessToken = (token: string): SetAccessTokenAction => ({
type: 'SET_ACCESS_TOKEN',
token,
});Then add the SetAccessTokenAction type in types.js:
...
/*
**************
* Synchronization
**************
*/
export type SetAccessTokenAction = {
type: 'SET_ACCESS_TOKEN',
token: string,
};
export type Action =
...
/** *** Synchronization **** */
| SetAccessTokenAction;Then modify the synchronization reducer as follows:
/*
* @flow
*/
import type { Action } from '../actions/types';
import type { SynchronizationState } from './types';
const initialState: SynchronizationState = {
userLoggedToDropbox: false,
accessToken: '',
};
const synchronizationState = (
state: SynchronizationState = initialState,
action: Action
): SynchronizationState => {
switch (action.type) {
case 'SET_ACCESS_TOKEN':
return { ...state, userLoggedToDropbox: true, accessToken: action.token };
default:
return state;
}
};
export default synchronizationState;Add accesToken to the SynchronizationState type:
export type SynchronizationState = {
+userLoggedToDropbox: boolean,
+accessToken: string,
};Finally, connect synchronization/index.js to redux and manage screen display according to state:
/*
* @flow
*/
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import type { ReduxState } from '../../reducers/types';
import * as synchronizationActions from '../../actions/synchronization';
import Synchronization from './Synchronization';
import Login from './Login';
type Props = {
navigation: Object,
actions: Object,
accessToken: string,
isLoggedIn: boolean,
};
const Index = (props: Props) => {
if (props.isLoggedIn) {
return (
<Synchronization
navigation={props.navigation}
accessToken={props.accessToken}
/>
);
}
return (
<Login
navigation={props.navigation}
setAccessToken={(token) => props.actions.SetAccessToken(token)}
/>
);
};
function mapStateToProps(state: ReduxState) {
return {
isLoggedIn: state.synchronization.userLoggedToDropbox,
accessToken: state.synchronization.accessToken,
};
}
export default connect(mapStateToProps, (dispatch) => ({
actions: bindActionCreators(synchronizationActions, dispatch),
}))(Index);Backing up, restoring, and deleting data
For data backup and download, we will use the react-native-fetch-blob library, which simplifies file access and data transfer.
Let's start by adding this library:
yarn add react-native-fetch-blobThe data will be saved in JSON format. The file will contain the encrypted passwords and the user's settings. We must also export technical data such as the salt, iv, and verificationToken so the data can be decrypted when it is restored.
Here is the format of the exported JSON object:
{
"d": "encrypted passwords",
"st": "user settings",
"s": "salt",
"v": "IV",
"vt": "verification token"
}Now we will move on to creating action types. In actions/types.js, add the following types:
/*
**************
* Synchronization
**************
*/
...
export type DropboxStartAction = {
type: 'DROPBOX_ACTION_START',
infos:string,
};
export type DropboxSuccessAction = {
type: 'DROPBOX_ACTION_SUCCESS',
info: string,
};
export type DropboxFailAction = {
type: 'DROPBOX_ACTION_FAIL',
error: string,
};
export type Action =
...
/** *** Synchronization **** */
| SetAccessTokenAction
| DropboxStartAction
| DropboxSuccessAction
| DropboxFailAction;Here we created three generic action types that will be used for the three actions (backup, restore, delete).
Now that the action types are created, we will modify the synchronization reducer:
/*
* @flow
*/
import type { Action } from '../actions/types';
import type { SynchronizationState } from './types';
const initialState: SynchronizationState = {
userLoggedToDropbox: false,
accessToken: '',
pendingAction: false,
message: '',
success: false,
};
const synchronizationState = (
state: SynchronizationState = initialState,
action: Action
): SynchronizationState => {
switch (action.type) {
case 'SET_ACCESS_TOKEN':
return { ...state, userLoggedToDropbox: true, accessToken: action.token };
case 'DROPBOX_ACTION_START':
return {
...state,
message: action.info,
success: false,
pendingAction: true,
};
case 'DROPBOX_ACTION_SUCCESS':
return {
...state,
message: action.info,
success: true,
pendingAction: false,
};
case 'DROPBOX_ACTION_FAIL':
return {
...state,
message: action.error,
success: false,
pendingAction: false,
};
default:
return state;
}
};
export default synchronizationState;All that remains is to add the new fields in the SynchronizationState type:
export type SynchronizationState = {
+userLoggedToDropbox: boolean,
+accessToken: string,
+pendingAction: boolean,
+message: string,
+success: boolean,
};To finish, add all the resources we will use:
fr.js
publishPending: 'Publication en cours ...',
pullPending: 'Récupération en cours ...',
deletePending: 'Supression en cours ...',
unHandled: 'Une erreur est survenue. Rééssayer plus tard',
notFound: 'Aucune sauvegarde trouvée',
notSameVerification: "Le mot de passe des données n'est pas le même que celui actuelle. Impossible de récupérer les données",
uploadSuccess: 'Les données ont été sauvegarder sur Dropbox avec succès',
dowloadSuccess: 'Les données ont été restaurer avec succès',
deleteBackupSuccess: 'Votre sauvegarde a été supprimé avec succès',en.js
publishPending: 'Publication in progress ...',
pullPending: 'Download in progress ...',
deletePending: 'Deleting ...',
unHandled: 'An error has occurred. Try again later',
notFound: 'No backup found',
notSameVerification: 'The data password is not the same as the current . Unable to retrieve the data',
uploadSuccess: 'The data was saved on Dropbox successfully',
dowloadSuccess: 'Data were successfully restore',
deleteBackupSuccess: 'Your backup has been deleted successfully',strings.js
publishPending:I18n.t('publishPending'),
pullPending:I18n.t('pullPending'),
deletePending:I18n.t('deletePending'),
unHandled:I18n.t('unHandled'),
notFound:I18n.t('notFound'),
notSameVerification:I18n.t('notSameVerification'),
uploadSuccess:I18n.t('uploadSuccess'),
dowloadSuccess:I18n.t('dowloadSuccess'),
deleteBackupSuccess:I18n.t('deleteBackupSuccess'),Data backup
Let's create the action that will back up data. In actions/synchronization.js:
/*
* @flow
*/
import RNFetchBlob from 'react-native-fetch-blob';
import Base64 from 'base-64';
import strings from '../locales/strings';
import type {
ThunkAction,
Dispatch,
SetAccessTokenAction,
DropboxStartAction,
DropboxSuccessAction,
DropboxFailAction,
} from './types';
/*
*** Actions ***
*/
export const SetAccessToken = (token: string): ThunkAction => (
dispatch: Dispatch
) => {
dispatch(setAccessToken(token));
};
// Actions
export function BackUpData(
token: string,
passwords: string,
iv: string,
salt: string,
verificationToken: string,
passwordLength: number,
autoGeneration: boolean
): ThunkAction {
return async (dispatch: Dispatch) => {
dispatch(startDropboxAction(strings.publishPending));
try {
const settings = { passwordLength, autoGeneration };
let data = {
d: passwords,
st: settings,
s: salt,
v: iv,
vt: verificationToken,
};
data = JSON.stringify(data);
const response = await RNFetchBlob.fetch(
'POST',
'https://content.dropboxapi.com/2/files/upload',
{
Authorization: `Bearer ${token}`,
'Dropbox-API-Arg': JSON.stringify({
path: '/data_backup.json',
mode: 'overwrite',
}),
'Content-Type': 'application/octet-stream',
},
Base64.encode(data)
);
if (response.respInfo.status.toString() !== '200') {
dispatch(dropboxActionFail(strings.unHandled));
}
dispatch(dropboxActionSuccess(strings.uploadSuccess));
} catch (error) {
dispatch(dropboxActionFail(error.toString()));
}
};
}
/*
*** Actions Creator ***
*/
const setAccessToken = (token: string): SetAccessTokenAction => ({
type: 'SET_ACCESS_TOKEN',
token,
});
const startDropboxAction = (info: string): DropboxStartAction => ({
type: 'DROPBOX_ACTION_START',
info,
});
const dropboxActionSuccess = (info: string): DropboxSuccessAction => ({
type: 'DROPBOX_ACTION_SUCCESS',
info,
});
const dropboxActionFail = (error: string): DropboxFailAction => ({
type: 'DROPBOX_ACTION_FAIL',
error,
});Next, we modify synchronization.js and index.js so we can execute the action.
First, define the Props type of the synchronization screen:
type Props = {
uploadBackup: () => void,
downloadBackup: () => void,
deleteBackup: () => void,
success: boolean,
message: string,
pendingAction: boolean,
navigation: Object,
};Then modify the methods for backup, restore, and data deletion:
uploadBackup() {
this.props.uploadBackup();
}
downloadBackup() {
this.props.downloadBackup();
}
deleteBackup() {
Alert.alert(strings.clear, strings.clearConfirmation, [
{ text: strings.cancel, style: 'cancel' },
{ text: strings.delete, onPress: () => this.props.deleteBackup() },
]);
}We will add an indicator to the screen so we can see whether the action is running, as well as text that shows the message once the action is finished.
Add a renderAction function like this:
renderAction() {
if (this.props.pendingAction) {
return (
<View style={styles.loaderCtnr}>
<ActivityIndicator size="large" color={WHITE} animating />
<Text style={styles.msgPending}>{this.props.message}</Text>
</View>
);
}
const color = this.props.success ? PRIMARY : DELETE_COLOR;
return <Text style={[styles.msg, { color }]}>{this.props.message}</Text>;
}Then add this function after the screen components like this:
render() {
return (
<View style={styles.container}>
...
{this.renderAction()}
</View>
);
}Finally, add the missing styles:
const styles = PlateformStyleSheet({
...
loaderCtnr: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba( 0, 0, 0, 0.7 )',
justifyContent: 'center',
alignItems: 'center',
},
msg: {
ios: {
padding: IOS_MARGIN,
},
android: {
padding: ANDROID_MARGIN,
},
},
msgPending: {
color: WHITE,
fontWeight: 'bold',
marginTop: 16,
},
});Now that the screen is ready, we will modify index.js to retrieve all the props defined in the Synchronization screen.
Start by defining the Props type in index.js:
type Props = {
navigation: Object,
actions: Object,
accessToken: string,
isLoggedIn: boolean,
success: boolean,
message: string,
pendingAction: boolean,
passwords: string,
iv: string,
salt: string,
verificationToken: string,
passwordLength: number,
autoGeneration: boolean,
};Then pass the necessary props to the Synchronization component:
const Index = (props: Props) => {
const {
isLoggedIn,
accessToken,
passwords,
iv,
salt,
verificationToken,
passwordLength,
autoGeneration,
} = props;
if (isLoggedIn) {
return (
<Synchronization
success={props.success}
pendingAction={props.pendingAction}
message={props.message}
navigation={props.navigation}
accessToken={props.accessToken}
uploadBackup={() =>
props.actions.BackUpData(
accessToken,
passwords,
iv,
salt,
verificationToken,
passwordLength,
autoGeneration,
)}
/>
);
}
return (
<Login
navigation={props.navigation}
setAccessToken={token => props.actions.SetAccessToken(token)}
/>
);We will add the downloadBackup and deleteBackup props as we add the actions.
Deleting data
We can move on to deleting the backup file on Dropbox. This will be relatively simple because we only need to create the action and add it to the Synchronization component.
Let's start with the action:
export function DeleteData(token: string): ThunkAction {
return async (dispatch: Dispatch) => {
dispatch(startDropboxAction(strings.deletePending));
try {
const req = {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: '{"path": "/data_backup.json"}',
};
const response = await fetch(
'https://api.dropboxapi.com/2/files/delete',
req
);
if (response.status.toString() === '409') {
dispatch(dropboxActionFail(strings.notFound));
} else if (response.status.toString() !== '200') {
dispatch(dropboxActionFail(strings.unHandled));
} else {
dispatch(dropboxActionSuccess(strings.deleteBackupSuccess));
}
} catch (error) {
dispatch(dropboxActionFail(error.toString()));
}
};
}Then add the prop on the component:
<Synchronization
success={props.success}
pendingAction={props.pendingAction}
message={props.message}
navigation={props.navigation}
accessToken={props.accessToken}
uploadBackup={() =>
props.actions.BackUpData(
accessToken,
passwords,
iv,
salt,
verificationToken,
passwordLength,
autoGeneration
)
}
deleteBackup={() => props.actions.DeleteData(accessToken)}
/>Restoring data
To finish this article, we will restore the data. This action is more complex than the previous two because we need to perform several actions inside it:
- Retrieve the data from Dropbox
- Check that the backup's verificationToken is similar to the application's
- Set the encrypted passwords in state with those from the backup
- Decrypt the passwords and set the passwords in state with the decrypted ones
- Set the settings state with the settings we just retrieved
We will start by adding the missing action types and modifying the reducers.
For settings, add the SetSettingsActions type in actions/types.js:
...
/*
**************
* Settings
**************
*/
...
export type SetSettingsAction = {
type: 'SET_SETTINGS',
length: number,
autoGeneration: boolean,
};
...
export type Action =
/** *** Settings **** */
| SetPasswordLengthAction
| SetAutoGenerationAction
| SetSettingsAction
...And modify the settings reducer this way:
const settingsState = (
state: SettingsState = initialState,
action: Action
): SettingsState => {
switch (action.type) {
case 'SET_PASSWORD_LENGTH':
return { ...state, passwordLength: action.length };
case 'SET_AUTO_GENERATION':
return { ...state, autoGeneration: action.autoGeneration };
case 'SET_SETTINGS':
return {
...state,
autoGeneration: action.autoGeneration,
passwordLength: action.length,
};
default:
return state;
}
};Moving on to unencrypted passwords, add the following action type:
export type SetPasswordsAction = {
type: 'SET_PASSWORDS',
passwords: NormalizedState,
};Then add the new case in the data reducer:
case 'SET_PASSWORDS':
return { ...state, passwords: action.passwords };Now we can create the action:
export function DownloadData(
token: string,
verificationToken: string,
key: CryptoJS.WordArray,
): ThunkAction {
return async (dispatch: Dispatch) => {
dispatch(startDropboxAction(strings.pullPending));
try {
const response = await RNFetchBlob.fetch(
'POST',
'https://content.dropboxapi.com/2/files/download',
{
Authorization: `Bearer ${token}`,
'Dropbox-API-Arg': '{"path": "/data_backup.json"}',
},
);
if (response.respInfo.status.toString() === '409') {
dispatch(dropboxActionFail(strings.notFound));
} else if (response.respInfo.status.toString() !== '200') {
dispatch(dropboxActionFail(strings.unHandled));
} else {
const data = JSON.parse(response.data);
if (verificationToken === data.vt) {
let uncryptedPasswords = Decrypt(data.d, key, data.v);
uncryptedPasswords = JSON.parse(uncryptedPasswords);
dispatch(setSettings(data.st.passwordLength, data.st.autoGeneration));
dispatch(setPasswords(uncryptedPasswords));
dispatch(updateCryptedPasswords(data.d));
dispatch(dropboxActionSuccess(strings.dowloadSuccess));
} else {
dispatch(dropboxActionFail(strings.notSameVerification));
}
}
} catch (error) {
dispatch(dropboxActionFail(error.toString()));
}
};
}Also add the missing action creators:
const setSettings = (
length: number,
autoGeneration: boolean
): SetSettingsAction => ({
type: 'SET_SETTINGS',
length,
autoGeneration,
});
const setPasswords = (passwords: NormalizedState): SetPasswordsAction => ({
type: 'SET_PASSWORDS',
passwords,
});
const updateCryptedPasswords = (
cryptedPassword: string
): UpdateCryptedPasswordsAction => ({
type: 'UPDATE_CRYPTED_PASSWORDS',
cryptedPassword,
});Then all that remains is to modify the Synchronization index.js file.
Here is the final index.js file:
/*
* @flow
*/
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import CryptoJS from 'crypto-js';
import type { ReduxState } from '../../reducers/types';
import * as synchronizationActions from '../../actions/synchronization';
import Synchronization from './Synchronization';
import Login from './Login';
type Props = {
navigation: Object,
actions: Object,
accessToken: string,
isLoggedIn: boolean,
success: boolean,
message: string,
pendingAction: boolean,
passwords: string,
iv: string,
salt: string,
verificationToken: string,
passwordLength: number,
autoGeneration: boolean,
cryptoKey: CryptoJS.WordArray,
};
const Index = (props: Props) => {
const {
isLoggedIn,
accessToken,
passwords,
iv,
salt,
verificationToken,
passwordLength,
autoGeneration,
cryptoKey,
} = props;
if (isLoggedIn) {
return (
<Synchronization
success={props.success}
pendingAction={props.pendingAction}
message={props.message}
navigation={props.navigation}
accessToken={props.accessToken}
uploadBackup={() =>
props.actions.BackUpData(
accessToken,
passwords,
iv,
salt,
verificationToken,
passwordLength,
autoGeneration
)
}
deleteBackup={() => props.actions.DeleteData(accessToken)}
downloadBackup={() =>
props.actions.DownloadData(accessToken, verificationToken, cryptoKey)
}
/>
);
}
return (
<Login
navigation={props.navigation}
setAccessToken={(token) => props.actions.SetAccessToken(token)}
/>
);
};
function mapStateToProps(state: ReduxState) {
return {
isLoggedIn: state.synchronization.userLoggedToDropbox,
accessToken: state.synchronization.accessToken,
success: state.synchronization.success,
message: state.synchronization.message,
pendingAction: state.synchronization.pendingAction,
passwords: state.cryptedData.passwords,
iv: state.user.iv,
salt: state.user.salt,
cryptoKey: state.data.key,
verificationToken: state.user.verificationToken,
passwordLength: state.settings.passwordLength,
autoGeneration: state.settings.autoGeneration,
};
}
export default connect(mapStateToProps, (dispatch) => ({
actions: bindActionCreators(synchronizationActions, dispatch),
}))(Index);This article is now finished. The application will be published to the app stores soon. Join the newsletter to be notified when it is released.