React Redux Cryptofrontend

This post describes how the Cryptofrontend was created.

Description of the Crypto-Application

The cryptoapp stores password entries in a server (which for this post is assumed to be working prerequisit) and it has a frontend that displays the entries to the user. The frontend will look very similar to an email client. On the left it lists all entries. The entries in the list are only showing part of the information so the user can quickly decide which entry he wants to use. Once the user clicks one of the entries, the entire data of that entry will be displayed on the right side of the application in a details view.

Basic CRUD operations on the password entries will be possible. If the user enters the master password in the details view, the password will be decrypted and copied into the users clipboard if that is possible. Otherwise the app just displays the plaintext password for the user to copy. The decryption and encryption happens in the user’s browser. Only encrypted data is transmitted to and from the backend.

Creating the Project Structure

Update node using the node version manager (nvm) to the latest version, otherwise the later steps probably will fail. First find the latest version using nvm ls-remote. Then install that version using nvm install <version>. nvm will automatically make installed version current so it is used when executing npm and node commands.

The first step is to generate the empty project structure using create-react-app command.

npx create-rect-app cryptofrontend

Make sure that the generation did finish successfully! The generator will create a cryptofrontend folder which can be opened in Visual Studio Code. If type yarn start inside the project folder, a server will be started and the site is automatically opened in a browser.

Organizing the Code

All actions and thunk functions go into the action folder. The action folder will contain all objects that can be dispatched. A better name for the actions folder would be dispatcheables.

All reducers go into the reducers folder.

The store creation goes into the store folder.

Installing the required npm Packages

npm i cross-fetch
npm i redux
npm i react-redux
npm i redux-logger
npm i react-thunk
npm i redux-thunk

Defining the Redux State

First let’s define what state the Redux container will contain. This is an example JavaScript Object that outlines what the state comprises. There is the id of the currently selected password entry. If no entry is selected, the value will not be there at all or it will be equal to or less than zero. After the selected id, there is an array of objects, each denoting one password entry.

{
  selected_entry_id: 1,
  entries: [
    {
      id: 1,
      password: 'ad2jd92jd9dj9j9'
    },
    {
      id: 2,
      password: 'sdfsdfsfsfsdfsdf'
    }
  ]
}

Retrieving Entries from the Server

Let’s develop the application in an iterative fashion by adding little bits of functionality until the app is complete. That way the entire complexity will not hit as as hard. In a sense this allows us to apply the principle of divide and conquer.

The first iteration will retrieve entries from the server and output the entries to the developer console. The entries have to be fetched from the backend and they have to be stored in the Redux store. Normally to alter the store, an action has to be dispatched and a reducer will create the new state given the current state and the values contained in the action. In order to talk to a backend, instead of dispatching an action and defining a reducer, the thunk middleware is used which makes it possible to dispatch a function.

This function is called fetchEntries(). Once fetchEntries() did receive the entries from the server, it will dispatch an action that carries the entries retrieved from the backend. This action will be handled by a reducer that inserts the entries into the state.

Reducers

Lets first talk about the reducers. As the first iteration only cares about the entries, there is only one reducer that insert entries into the store. The root reducer will only consist of this one reducer.

import { combineReducers } from 'redux'
import {
  RECEIVE_ENTRIES
} from '../actions/actions.js'

// deals with entries
function entriesReducer(state = {}, action) {

  // switch over all action types
  switch (action.type) {

    // entries were retrieved from the backend
    case RECEIVE_ENTRIES:

      // this reducer will take the entries from the action and put copy
      // them into the store
      return Object.assign({}, state, {
        entries: action.entries
      })

    default:
      return state
  }
}

The entriesReducer only reacts to the RECEIVE_ENTRIES action and it will just copy the entries that the action contained into the store. The file also builds the root reducer:

// build the rootReducer from several partial reducers
const rootReducer = combineReducers({
  entriesReducer
});

// default export of this file
export default rootReducer;

The root reducer currently only consists of the entriesReducer because there is no other reducer right now. (A currentlySelectedEntryReducer will follow in upcoming iterations).

The next thing needed is the RECEIVE_ENTRIES action itself.

Actions, Thunks and other Dispatcheable Objects

import fetch from 'cross-fetch'

export const RECEIVE_ENTRIES = 'RECEIVE_ENTRIES'

// this function is called an action creator as it constructs
// and returns an action
function receivePostsActionCreator(json) {
  console.log('receivePostsActionCreator ', json);
  return {
    type: RECEIVE_ENTRIES,
    entries: json
  }
}

// this function is a thunk. It is dispatcheable and performs
// the server communication. Once it is done, it will dispatch
// call the action creator to dispatch the RECEIVE_ENTRIES action
export function fetchEntriesThunk() {
  console.log('fetchEntriesThunk');
  return dispatch => {
    return fetch(`http://localhost:8081/todos`)
      .then(response => response.json())
      .then(json => dispatch(receivePostsActionCreator(json)))
      .catch((error) => {
        console.log("reset client error-------", error);
      });
  }
}

This file contains one action creator and one thunk. The thunk will use the action creator to dispatch the RECEIVE_ENTRIES action once it has retrieved all entries from the backend. The entries are put into the RECEIVE_ENTRIES action by the action creator. The action is dispatched and handled by the reducer defined in the last section. The reducer will create a new state from the entries contained in the action. This is how the entries travel from the backend all the way into the store.

Store Creation

This file creates a store and adds the thunk and logger middleware:

import rootReducer from '../reducers/reducers.js';
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'

const loggerMiddleware = createLogger()

function configureStore(preloadedState) {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunkMiddleware, loggerMiddleware)
  )
}

export default configureStore;

The configureStore() function is exported. It creates the store and adds the rootReducer. It also allows to add an optional initial state, which we do not use. Also, it applies middleware. It applies the thunk middleware which allows for dispatching functions instead of only action objects. It applies a loggerMiddleware which logs every state change to the developer console.

In the app.js file, testing code is added. The testing code will also fulfill all our goals that we set for the first iteration. It creates the store, dispatches the thunk and by doing so, retrieves data from the server and inserts that data into the store.

import configureStore from './store/store.js'
import { fetchEntriesThunk } from './actions/actions.js'

// create a store
const store = configureStore();

// Log the initial state
//console.log(store.getState());
store.dispatch(fetchEntriesThunk());
//console.log(store.getState());

When you load the cryptofrontend in your browser, this code is executed and the backend is accessed. In the developer tools console of your browser, you should see all the state changes that the store is going through.

Summary for the First Iteration

Without using any components, the goal of the first iteration was to integrate the Redux Store to the backend server. There is now a thunk that GETs all entries from the backend.

Adding an Entry into the Redux Store

This thunk will POST an entry to the redux store:

export function addEntryThunkActionCreator() {

  // DEBUG
  console.log('addEntryThunkActionCreator');

  // return the thunk
  return dispatch => {
    return fetch(`http://localhost:8081/todos/add`, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ id: 0, password: 'Textual content' })
    })
      .then(response => response.json())
      .catch((error) => {
        console.log("POST addentry error ", error);
      });
  }
}

Note that the new entry is not added to the Redux Store, it is only posted to the backend. Given this code, the app has to dispatch the thunk that retrieves all entries to sync up its state with the backend store. An alternative would be to dispatch an action that appends the new entry to the Redux store state in the success case. This alternative saves a lot of network traffic. The following code defines an action to append a new entry to the Redux store.

Selecting an Entry in the Redux Store

The next iteration will add an action for selecting one of the entries. The idea is that after selecting an entry, the details view has to render all the entries details. The currently selected item is actually part of the state in the store. That is why the store will be extended by an action for selecting an entry. Thanks for reading and stay tuned for the next iteration.

Building the components

 

Leave a Reply