NodeJS/React Multiplayer Space Sim walkthrough

Update 31/12/2018 - www.cmdship.net

This started out from the prior mmo game blog post and spiraled out of control somewhat. I will probably break it into parts as I expand on sections that are not clear, but for now it is in a single post. www.cmdship.net should usually have a copy of the app running on it, although it may differ slightly at times. My current idea is to develop a multiplayer RPG sim on there using the source below - the details of which I will put into a post once the idea is fully formed.

Overview

So I am going to build an browser based multiplayer space game/sim using NodeJS/React/MongoDB and HTML5 canvas, with an ingame cryptocurrency and blog the entire thing as I do so. As I build the part I will bung in the code or alterations, attempt to explain what it does/should do - which if you manage to decipher and understand should end up looking something like the following image. The latest source will be at: https://github.com/dbjsdev/cmdship. At the end of each part you will find a link to the source code for that part.

mainimage

Parts so far..

Part 1 - The Idea and Initial framework

Warning: This isn’t intended to be a guide on what is the best way to do things - I am making this up as I go, and then later coming back to fix it up - the further it goes the more mad it may get and performance will probably be terrible. It will routinely include code that probably violates all sorts of rules and some of it may not even make any sense due to the nature of how the early parts were written. I do however plan to keep returning and updating the prior sections as I work and improve the code. Some of the comments were written as I wrote the post, some I have added in later for clarity and will attempt to do this further as I get time. At times it may just seem like endless walls of code pasted in - as it progresses further this will hopefully reduce. At the end of each part I will put in a link to the source up until that point. If you spot any issues, or have ideas for how parts can be improved/done better, feel free to leave a comment at the bottom of the post or contact me and I will try implement it.

The basic idea is a browser based sim, with simple polygons and shapes only - no flash graphics, html5 canvas only,whereby a player registers/connects and is then loaded into a 2d game world in space, controlling a spaceship. The user can eject from the spaceship, and fly about as a PTU(a small pod thing kind of like in eve), and board other empty spacecraft. The game world will be broken into Zones or areas that represent star systems. Users can then move between star systems using a series of stargates. The player ship will have a hyperdrive which lets them move between locations inside a star system, and also move the ship using thrust and direction keys and also an autopilot. The ship will have modules which can fitted such as a cannon, mining laser, shield etc. The game interface will have an item system which allows cargo to moved between locations such as a storage object and the ships cargo hold. The player can also dock with space stations and store vehicles and cargo there. I am also going to integrate a cryptocurrency into the app for the in-game currency.

This app is going to consist of two parts. A frontend client which renders the game world to a canvas with React/Redux handling parts of the UI and interface. On the server side there will be a NodeJS server which handles the login/registration and runs the game server with data being persisted to a MongoDB database. The communication between client/server will be done with Express and Socket.io.

My initial task is going to be to build the basic framework for the client and server communicate, login in the user. To start this I will first get Express setup, delivering the client React app built using webpack.

I am going to whizz through and only briefly cover this intial express/react setup. You can find more details on what exactly is going on with some of this in my other posts, or by looking around on google which can explain webpack in more detail. I’ve started by creating a new repo and clone it locally and setup the basic folder structure we’ll use. (Incase it isn’t clear, when in the code snippets I use ‘…’, it is it to make it clear ive ommited sections of the file that I havn’t changed)

mkdir cmdship
cd cmdship
echo "cmdship" > README.md
mkdir dist
mkdir logs
mkdir src
cd src
mkdir server
mkdir client
mkdir common
cd ..

I then ran “npm init”, setting my main entry to ‘src/server/main.js’

I am creating this off using a template I already had so instead of installing what I need via a terminal I copied in the following into my package.json. (Some of these versions you’ll see in this tutorial are what they are due to compatability issues encountered later. It’s possible the issues are fixed by the time you are reading this)

Update your dependencies in package.json to the following and also be sure to add the scripts section.

{
  "name": "cmdship",
  "version": "1.0.0",
  "description": "cmdship",
  "main": "src/server/main.js",
  "scripts": {
    "start": "babel-node $npm_package_main --presets env",
    "start-debug": "babel-node $npm_package_main --inspect-brk --presets es2015",
    "start-debug-watch": "babel-watch $npm_package_main --inspect-brk --presets es2015",
    "start-dev": "babel-watch $npm_package_main --presets es2015",
    "nodemon": "nodemon $npm_package_main --exec babel-node --presets es2015",
    "postinstall": "webpack --display-error-details",
    "build": "webpack",
    "build-watch": "webpack --watch",
    "stats": "webpack --env production --profile --json > stats.json"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-cli": "^6.26.0",
    "babel-loader": "^7.1.5",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "cors": "^2.8.4",
    "css-loader": "^1.0.0",
    "dotenv": "^6.0.0",
    "es2015": "0.0.0",
    "eventemitter3": "^1.2.0",
    "express": "^4.14.0",
    "html-webpack-plugin": "^3.2.0",
    "query-string": "^4.2.3",
    "react": "^16.4.2",
    "react-dom": "^16.4.2",
    "react-scripts": "1.1.4",
    "style-loader": "^0.23.0",
    "webpack": "^3.8.1"
  }
}

I then installed the above with

npm install

I created a .gitignore file on the root containing the /node_modules folder.

node_modules/
logs/

I created webpack.config.js on the root.

const path = require('path');
const fs = require('fs');

module.exports = {
  entry: './src/client/app.js', 
  output: {
      path: path.join(__dirname, 'dist'),
      filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.css$/, loader: "style-loader!css-loader" },
      {
          test: /\.scss$/,
          loaders: ['style-loader', 'raw-loader', 'sass-loader']
      },
      { test: /\.(js|jsx)$/, loader: 'babel-loader', options: {
        presets: ['react','env']
      }, exclude: /node_modules/ }
    ]
  },
  resolve: {
      extensions: ['.js', '.jsx'],
  }
};

Next I created an index.html in my root folder

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>CMDSHIP</title>
    <meta charset="utf-8">
  </head>
  <body>
    <div id="root"></div>
    <script src="./bundle.js" type="text/javascript"></script>
  </body>
</html>

I then created app.js in src/client/ containing the following basic react code which will output Hello in an H1.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

export default class App extends Component {
  render () {
    return (
      <div>
        <h1>Hello</h1>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

Next in order to run our express server I created the main.js within /src/server/ which we specified in our package.json.

import path from 'path';
import express from 'express';

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, '../../index.html');

const server = express();

server.get('/', function(req, res) { res.sendFile(INDEX); });
server.use('/', express.static(path.join(__dirname, '../../dist')));

server.listen(PORT, () => console.log(`Listening on ${ PORT }`));

This code just sets up our express server and serves up our index.html on port 3000 to incoming requests.

Finally in order to run this to make sure it works:

npm run-script build
...
npm start

This screenshot of the folder structure in sublime and also the commands run at the terminal should give you an idea of what you should end up with.

folderstructpart1

hello

Part 2 - Authentication

The next step in our framework is going to be authentication. When a user connects to our game we want them to be able to first login and register.

We are going to use express to setup an API at /user with endpoint for register, login and logout. These will make calls to a UserManager which uses Mongoose/MongoDB to authenticate, or register the user.

On our client React app we will have an interface with forms for register and login which will make API calls to the server. When the user logs in or registers successfully, on the server we’ll create them a JWT token, which is sent back to the client and stored. This will be used in subsequent communications to authenticate them.

So first, lets setup the server side API. We’ll start by creating some new folders within /src/server

Before going further I am going to create a logger as we are going to want to see logs of events that happen on the server. We are going to use winston to do this.

Note: Through the subsequent chapers you will notice at times the logging appears incomplete or just wierd - this is due to being added in later on but then moved up here in the walkthrough.

npm install winston --save

So we’ll create /src/server/logger.js with the following code:

const winston = require('winston');

winston.level = process.env.LOG_LEVEL;

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp({
      format: 'YYYY-MM-DD hh:mm:ss A ZZ',
    }),
    winston.format.json(),
  ),
  transports: [
    new winston.transports.File({ filename: './logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: './logs/combined.log' }),
  ],
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple(),
  }));
}

module.exports = logger;

This will create a log file error.log which will contain purely errors in order for us to see them more easily should they occur, and another log file combined.log which contains everything. You’ll see this being used further down.

We also want to create a .env file here also… As you’ll see in our initial dependencies list we installed dotenv. This lets us set environment vars in a .env file on the root, which are then read in to be used by referencing process.env.variableName. We’ll use this for storing a few things such as our mongodb address, port and things like our JWT secret which we’ll come to in a bit. Below are the vars you need, with some example values which will need to adjust accordingly depending on your db.

(This needs to be in the root, the same folder as package.json and README.md)

/.env

NODE_ENV=DEVELOPMENT
PORT = 3000
MONGO_DB_URL = mongodb://127.0.0.1:27017/mmonode
LOG_LEVEL=DEBUG
JWTSECRET = dappyNdubz

Next we can create our User model, we need mongoose, and you’ll need mongodb installed. I used a local install of Mongo DB version 4.0.3. We also install bcrypt for our password hashing.

npm install mongoose bcrypt --save

For our User model we will start with the following in /src/server/model/user.js

import mongoose from 'mongoose';
import bcrypt from 'bcrypt';

const SALT_WORK_FACTOR = 10;
const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    index: { unique: true },
    required: true
  },
  password: { type: String, required: true },
  created: {
    type: Date,
    required: true,
    default: new Date(),
  },
  status: { type: Number, default: 2 }
});

UserSchema.methods.comparePassword = function comparePassword(password, callback) {
  bcrypt.compare(password, this.password, callback);
};

// On save, hash the password

UserSchema.pre('save', function saveHook(next) {
  let user = this;

  if (!user.isModified('password')) {
    return next();
  }

  return bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
    if (err) { return next(err); }

    return bcrypt.hash(user.password, salt, (hashError, hash) => {
      if (hashError) {
        return next(hashError);
      }
      user.password = hash;
      return next();
    });
  });
});


module.exports = mongoose.model('User', UserSchema);

We’ll add more to this User model later but for now this code gives us a user model with our username, password, status and datecreated with a hook on the save method which will has the password before saving. We then have a compare method on the schema for checking a login attempt against the hashed password.

We’ll now create our UserManager.

This is where we will do all our user related activity, in both our api and also later on in our game server. This will use the UserModel we just created. We’ll create this file in /src/server/managers/userManager.js

import logger from '../logger';
import UserModel from '../model/user';

const MIN_USERNAME = 2;
const MIN_PASSWORD = 2;

class UserManager {
  constructor() {

  }

  loginUser(username, password) {
    return new Promise((resolve, reject) => {
      if (username === undefined || password === undefined) {
        reject(new Error('Incorrect username/password'));
        return;
      }

      if (username.length < MIN_USERNAME || password.length < MIN_PASSWORD) {
        reject(new Error('Incorrect username/password'));
        return;
      }

      UserModel.findOne({ username }, (err, user) => {
        if (err) {
          logger.error('Error attempting to find(UserModel) in UserManager');
          reject(new Error('System error'));
          return;
        }

        if (!user) {
          logger.info('User not found');
          reject(new Error('Incorrect username/password'));
          return;
        }

        user.comparePassword(password, (err, isMatch) => {
          if (err) {
            logger.error('Error in compare password');
            reject(new Error('System error'));
            return;
          }

          if (isMatch) {
            resolve(user);
            return;
          }

          logger.info('Invalid password');
          reject(new Error('Incorrect username/password'));
        });
      });
    });
  }

  logoutUser(userID) {
    return new Promise((resolve, reject) => {
      resolve(userID);
    });
  }

  registerNewUser(username, password) {
    return new Promise((resolve, reject) => {
      if (username === undefined || password === undefined) {
        return resolve({result: false, message: "Invalid registration details"});
      }

      if(password.length < 6){
        return resolve({result: false, message: "Invalid registration details: Not long enough"});
      } else if (password.length > 20) {
        return resolve({result: false, message: "Invalid registration details: Too long"});
      } else if (password.search(/\d/) == -1) {
        return resolve({result: false, message: "Invalid registration details: No digit"});
      } else if (password.search(/[a-zA-Z]/) == -1) {
        return resolve({result: false, message: "Invalid registration details: No letter"});
      } else if (password.search(/[^a-zA-Z0-9\!\@\#\$\%\^\&\*\(\)\_\+\.\,\;\:]/) != -1) {
        return resolve({result: false, message: "Invalid registration details: Invalid character"});
      }

      if (username.search(/^[a-zA-Z0-9_]{5,}[a-zA-Z]+[0-9]*$/) == -1) {
        return resolve({result: false, message: "Invalid registration details: Invalid username"});
      }

      UserModel.findOne({ username }, (err, userfound) => {
        if (err) {
          logger.error('Error in UserManager reg');
          reject(new Error('System error'));
          return;
        }
        
        if(userfound) {
        return resolve({result: false, message: "Username already exists"});
        }

        let newUser = new UserModel({
          username,
          password,
        });

        newUser.save((err, user) => {
          if (err) {
            logger.error('Error creating user in UserManager save');
            reject(new Error('System error'));
            return;
          }

          return resolve({result: true, user: user});
        });
      });
    });
  }
  
  verifyActiveToken(userID, token) {
    return true;
  }  
}

module.exports = UserManager;

You’ll see we have three methods loginUser, logoutUser and registerNewUser all of which return a promise back to wherever called it. This ensures nothing is hanging around waiting whilst our queries occur. We also import our logger, and have some temp error messages in there. Our logging occurs simply through importing the logger and then calling logger.error or logger.info, with our message being written to the appropriate log file. The logoutUser doesn’t really do anything yet but will later on.

The method verifyActiveToken is not implemented yet but will be where our code can add the JWT to a list so that it can be blacklisted after the user logs out.

You will also notice some odd very error handling/throwing going on in the above and the code that follows. This is temporary and is going to be reworked later on.

Next we’ll create our controller for our API which will make the calls to our UserManager. This is going to make use of JWT tokens (you can read about these in my prior React/NodeJS tutorial or google so I’ll skip the explaination here) so I’ll install that first.

npm install jsonwebtoken --save

Then within /src/server/controllers , create the file authController.js with the following code.

const jwt = require('jsonwebtoken');
const logger = require('../logger');
const UserManager = require('../managers/userManager');

const userManager = new UserManager();

module.exports = {
  login: (username, password, callback) => {
    userManager.loginUser(username, password)
      .then((user) => {
        logger.info(`${username} logged in successfully.`);
        callback(null, { success: true, userid: user._id, username: username ,tokenID: jwt.sign({ username: user.username, _id: user._id}, process.env.JWTSECRET)});
      })
      .catch((e) => {
        if (e.message === 'System error') {
          callback(e, null);
        } else {
          callback(null, { success: false });
        }
      });
  },
  logout: (userid, username, callback) => {
    userManager.logoutUser(userid)
      .then((userid) => {
        logger.info(`${username} logged out.`);
        callback(null, { success: true });
      })
      .catch((e) => {
        console.log(e);
        if (e.message === 'System error') {
          callback(e, null);
        } else {
          callback(null, { success: false });
        }
      });
  },
  register: (username, password, callback) => {
    userManager.registerNewUser(username, password)
      .then((response) => {
        if(response.result){
          logger.info(`${username} Registered`);
          callback(null, {
            success: true,
            userid: response.user._id,
            username: username,
            tokenID: jwt.sign({ username: response.user.username, _id: response.user._id}, process.env.JWTSECRET)
          });
        } else {
          callback(null, {
            success: false,
            error: response.message
          });
        }
      }).catch((e) => {
        if (e.message === 'System error') {
          callback(e, null);
        }
      });
  },
};

So in there you’ll see the controller had 3 functions, login, logout and register.

The login function takes a username and password as arguments, and a callback and then makes a call to our UserManager’s login function, passing in those args. You’ll see we then do “.then(“, which means the returned Promise that our UserManager login method returns, has completed successfully. It then calls the callback passing in an object that contains a list of variables such as userid and username, and critically the JWT tokenID, which is signed using the userid, username and our JWT secret which we set in our .env environments file. If the UserManager login method fails to login the user in it will return an error which is caught by our “.catch” (I am going to alter this at somepoint to an alternate solution), and the callback is fired with “success: false”.

Logout does not really do anything beyond call the logout method in userManager (which does not do much yet beyond return true) and register is similar to login except it is calling the userManager.registernewuser method, creating a new user and then just like with login we create the JWT tokenID and return it in our callback.

Continuing on we now need our routes file which calls the above controller. Within /src/server/routes , create the file user.js with the following

const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/logout', (req, res, next) => {
  authController.logout(req.userData.userid, req.userData.username, (err, result) => {
    if (err) {
      console.log(err);
      res.status(500).json({
        success: 0,
        error: err,
      });
      return;
    }

    if (result.success) {
      res.status(200).json({
        success: 1
      });
    } else {
      res.status(401).json({
        success: 0,
        error: '',
      });
    }
  });
});

router.post('/login', (req, res, next) => {
  authController.login(req.body.username, req.body.password, (err, result) => {
    if (err) {
      console.log(err);
      res.status(500).json({
        success: 0,
        error: err,
      });
      return;
    }

    if (result.success) {
      res.status(200).json({
        success: 1,
        tokenID: result.tokenID,
        username: result.username,
        userid: result.userid,
      });
    } else {
      res.status(401).json({
        success: 0,
        error: '',
      });
    }
  });
});

router.post('/', (req, res, next) => {
  authController.register(req.body.username, req.body.password, (err, result) => {
    if (err) {
      console.log(err);
      res.status(500).json({
        success: 0,
        error: err,
      });
      return;
    }
    if (result.success) {
      res.status(200).json({
        success: 1,
        tokenID: result.tokenID,
        username: result.username,
        userid: result.userid
      });
    } else {
      res.status(400).json({
        success: 0,
        error: result.error
      });
    }
  });
});

module.exports = router;

In the above file we have 3 posts: ‘/logout’, ‘/login’ and ‘/’ (our register). These make calls to the methods in the authController we just made. Upon a successful login or register callback we return a status 200 back to the client and pass back a structure containing the JWT tokenID, username, userid and a status.

One note about the /logout route. You might notice it uses “req.userData.userid” and “req.userData.username”. This does not exist yet. This is a structure which our authentication middleware will pass in which will contain the userid based on their JWT. In order for this to happen we will next create this middleware. In /src/server/middleware/ create the file authCheck.js with the following code:

const jwt = require('jsonwebtoken');
const UserManager = require('../managers/userManager');

const um = new UserManager();

module.exports = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).end();
  }

  const token = req.headers.authorization.split(' ')[1];

  return jwt.verify(token, process.env.JWTSECRET, (err, decoded) => {
    if (err) {
      return res.status(401).end();
    }

    if (!um.verifyActiveToken(decoded._id, token)) {
      return res.status(401).end();
    }

    req.userData = {};
    req.userData.tokenID = token;
    req.userData.userid = decoded._id;
    req.userData.username = decoded.username;

    return next();
  });
};

This middleware code takes the JWT, decodes it, and then extracts the users userid and username and places it into the request data for our further routes to use. This helps to ensure the user is who they say they are. You’ll note the use of UserManager.verifyActiveToken - that would be where our yet to be implemented JWT blacklist was checked against. This would help stop anyone reusing someone elses JWT after they log out if they were in some way able to get hold of it.

The final step is now to update our /src/server/main.js file to take into account these changes.

import path from 'path';
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
dotenv.config()

import logger from './logger';
import userRoutes from './routes/user';
import authCheckMiddleware from './middleware/authCheck';

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, '../../index.html');

const dbURL = process.env.MONGO_DB_URL;

const server = express();

server.use(cors());
server.options('*', cors());
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));

server.use('/user/logout', authCheckMiddleware);
server.use('/user', userRoutes);

server.get('/', function(req, res) { res.sendFile(INDEX); });
server.use('/', express.static(path.join(__dirname, '../../dist')));

let requestHandler = server.listen(PORT, () => console.log(`Listening on ${ PORT }`));

new Promise((resolve, reject) => {
  mongoose.connect(dbURL, (err) => {
    if (err) {
      logger.error(`Error connecting to: ${dbURL}`);
      return reject(err);
    }
    logger.info('Connected to MongoDB server successfully.');
    return resolve();
  });
}).then(() => {
  logger.info('Ready to initalise game server...');
});

You’ll see we setup our routes on /user and prepare our mongodb connection ready for our game server to be intiialised later on.

We now have a functioning API for registering and logging a user in. The calls can come into our express server, route into our router user.js, which then calls our authController, this in turn calls userManager which checks against/registers to our mongodb. authController takes the result, generates our JWT if needs be, returns the result in an object to the router which then sends the result back to the client.

We now need to build our client which will make the calls to our API.

The next few steps will add quite a few things in. We are going to integrate in react redux. Our store, reducer and actions. Some of the styling here for the UI stuff here is a bit hacked together.

First for our redux structure we’ll create 4 folders, /src/client/actions, /src/client/constants/, /src/client/reducers, /src/client/stores.

We need to install a few things here for react redux

npm install redux react-redux redux-thunk --save

I then installed reactstrap. As a lot of the UI in what follows is a bit of a bodge and if you don’t use the same version numbers I used it is possible you may get issues.

To install what I did was:

npm install --save bootstrap@4.1.3 reactstrap@6.4.0

( There are some instructions here that might help if you have problems: https://www.npmjs.com/package/reactstrap )

Beginning with our redux setup, within /src/client/constants/ create the file actionTypes.js with the following.

export default {
  USER_LOGGED_IN: 'user_logged_in',
  USER_LOGGED_OUT: 'user_logged_out',
  UPDATE_AUTHENTICATIONMESSAGE: 'update_authenticationmessage',
};

Next within /src/client/reducers/ create the file worldReducer.js. In this app we are going to only have a single reducer for everything.

import actionTypes from '../constants/actionTypes';

let initialState = {
  authenticationResponse: localStorage.getItem('token') ? 'Ready to launch' : 'Awaiting login/registration...',
  loggedInStatus: localStorage.getItem('token') ? true : false,
  userid: localStorage.getItem('userid') ? localStorage.getItem('userid') : '',
  username: localStorage.getItem('username') ? localStorage.getItem('username') : ''
};

export default (state = initialState, action) => {
  let updated = Object.assign({}, state)
  switch (action.type) {
    case actionTypes.UPDATE_AUTHENTICATIONMESSAGE:
      updated['authenticationResponse'] = action.msg;
      return updated;
    case actionTypes.USER_LOGGED_IN:
      updated['username'] = action.username;
      updated['userid'] = action.userid;
      updated['loggedInStatus'] = true;
      return updated;
    case actionTypes.USER_LOGGED_OUT:
      updated['username'] = '';
      updated['userid'] = '';
      updated['loggedInStatus'] = false;
      return updated;
    default:
      return state;
  }
};

The above implements our three actions, storing the username, userid, loggedInStatus. You’ll also notice we default our initial state to include items in local storage. This is where we are going to store our JWT returned from the server and our returned userid. This will assist us if the user refreshes the browser by retaining the token so that it immediatly loads them back into the game without needing to log in again.

Next within /src/client/actions/ create the file actions.js as follows:

import actionTypes from '../constants/actionTypes';

export const logoutUser = (socket) => {
  var token = localStorage.getItem('token') || null;

  return (dispatch) => {
    dispatch(updateAuthenticationResponseMessage('Logging out'));
    dispatch(updateAuthenticationResponseMessage('Logged out successfully'))
    dispatch(userLoggedOut());
    localStorage.clear();

    return fetch('http://localhost:3000/user/logout', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization' : `Bearer ${token}`
      },
      body: JSON.stringify({}),
      mode: 'cors'})
    .then( (response) => {
      if (!response.ok) {
        localStorage.clear();
        throw Error(response.statusText);
      }
      return response.json();
    })
    .then( (data) => {
      localStorage.clear();
    })
    .catch( (e) => console.log(e) );
  }
}

export const loginUser = (socket, username, password) => {
  const data = { username, password };

  return (dispatch) => {
    dispatch(updateAuthenticationResponseMessage('Attempting login..'))

    return fetch('http://localhost:3000/user/login', {
      method: 'POST',
       headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      body: JSON.stringify(data),
      mode: 'cors'})
    .then( (response) => {
      if (!response.ok) {
        dispatch(updateAuthenticationResponseMessage('Incorrect username/password'));
        throw Error(response.statusText);
      }
      return response.json();
    })
    .then( (data) => {
      localStorage.setItem('username', data.username);
      localStorage.setItem('userid', data.userid);
      localStorage.setItem('token', data.tokenID);

      dispatch(updateAuthenticationResponseMessage('Ready to launch.'))
      dispatch(userLoggedIn(data.username, data.userid));
    })
    .catch( (e) => {
      dispatch(updateAuthenticationResponseMessage('System offline'));
      console.log(e)
    });
  }
}


export const registerNewUser = (socket, username, password) => {
  const data = { username, password };
  return dispatch => {
    dispatch(updateAuthenticationResponseMessage('Attempting registration..'))

    return fetch('http://localhost:3000/user/', {
      method: 'POST',
       headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      body: JSON.stringify(data),
      mode: 'cors'})
      .then( (response) => {
        if (!response.ok) {
          if(response.status==400){
            return response.json();
          }
          throw Error(response.statusText);
        }
        return response.json();
      })
      .then( (data) => {
        if(data.success){
          localStorage.setItem('username', data.username);
          localStorage.setItem('userid', data.userid);
          localStorage.setItem('token', data.tokenID);

          dispatch(updateAuthenticationResponseMessage('Registered successfully and logged in'))
          dispatch(userLoggedIn(data.username, data.userid));
        } else {
          dispatch(updateAuthenticationResponseMessage(data.error))
        }
      })
      .catch( (e) => {
        dispatch(updateAuthenticationResponseMessage('System offline'));
      });
  }
}


export const userLoggedIn = (username, userid) => {
    return {
        type: actionTypes.USER_LOGGED_IN,
        username: username,
        userid: userid
    }
}

export const userLoggedOut = () => {
    return {
        type: actionTypes.USER_LOGGED_OUT
    }
}

export const updateAuthenticationResponseMessage = (msg) =>{
    return {
        type: actionTypes.UPDATE_AUTHENTICATIONMESSAGE,
        msg: msg,
    }
}

The key things in there are our loginUser, logoutUser and registerNewUser. Each one dispatches “updateAuthenticationResponseMessage”, passing in a message in order to update the UI on an authentication box which we will build shortly. They then do a fetch request against the API calling the relevant resource, gets the response, updates properties in local storage (or in the case of logout, deletes them), then dispatches a login/logout/register action and finally invokes “updateAuthenticationResponseMessage” to inform the UI with what has happened.

We now need to make our store. In /src/client/stores/ create store.js with the following:

import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import worldReducer from '../reducers/worldReducer';

const store = createStore(
  combineReducers({
    world: worldReducer,
  }),
  applyMiddleware(
    thunk,
  ),
);

export default store;

With that in place we can now build the foundations of our UI which will dispatch the actions.

We will alter our ./src/client/app.js to be the following:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import 'bootstrap/dist/css/bootstrap.min.css';

import store from './stores/store';
import CmdshipClient from './cmdshipClient';

export default class App extends Component {
  render () {
    return (
      <div>
        <Provider store={store}>
          <CmdshipClient />
        </Provider>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

In there we have added in redux and our store and also you will notice we have imported the bootstrap css for our UI.

Next up we will create that new cmdshipClient.js in /src/client

import React, { Component } from 'react';
import WindowSystem from './UI/windowSystem';

class CmdshipClient extends Component {

  render() {
    return (
      <div>
        <WindowSystem style={{zIndex:1 , position: 'absolute', top:0, left:0, width: 200}}/>
      </div>
    );
  }
}

export default CmdshipClient;

For now don’t worry why that is called WindowSystem (It is going to form the basis of some mental draggable window UI later on).

We’ll make a UI folder within /src/client and then create windowSystem.js within /src/client/UI

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Authentication from './authentication';

class WindowSystem extends Component{
  constructor(props){
    super(props);
  }

  render(){
    const styles = {
      top: 0,
      left: 0,
      width: window.innerWidth,
      height: window.innerHeight,
      position: 'absolute',
      color: '#000000',
    }

    return (
      <div style={styles} id="wr">
        <Authentication />
      </div>
    );
  }
}

const mapStateToProps = state => {
    return {
        world: state.world
    }
}

export default connect(mapStateToProps)(WindowSystem);

This for now will just show our Authentication box.. which we will build in /src/client/UI/authentication.js with the following code:

(Warning: The HTML and CSS in here (and onwards) is a bit naff (as is usual with all html/css/front end I do) - In a later section it will get tidied up slightly )

import React, { Component } from 'react';
import { connect } from 'react-redux'
import { Container, Col, Row, Button, Form, FormGroup, Label, Input, FormText } from 'reactstrap';
import {
  registerNewUser,
  loginUser,
  logoutUser,
  updateAuthenticationResponseMessage
} from '../actions/actions.js';

class Authentication extends Component {

  constructor () {
    super();

    this.state = {
      username: '',
      password: '',
      regUsername: '',
      regPassword: '',
      regPassword2: '',
      toggleReg: false
    }
  }

  componentDidMount(){
    
  }

  handleRegisterButtonClick(e){
    if(this.state.regPassword !== this.state.regPassword2){
      this.props.dispatch(updateAuthenticationResponseMessage('Error: Passwords do not match'));
    }else{
      this.props.dispatch(registerNewUser(this.props.socket, this.state.regUsername, this.state.regPassword));
      this.setState({regUsername: '', regPassword: '', regPassword2: ''});
    }
  }

  handleLaunchButtonClick(e){

  }

  handleDisconnectButtonClick(e){

  }

  handleLoginButtonClick(e){
    this.props.dispatch(loginUser(this.props.socket, this.state.username, this.state.password));
    this.setState({ password: '' });
  }

  handleLogoutButtonClick(e){
    this.props.dispatch(logoutUser(this.props.socket));
  }

  handleUsernameChange(e) {
    this.setState({username: e.target.value});
  }

  handlePasswordChange(e) {
    this.setState({password: e.target.value});
  }

  handleRegUsernameChange(e) {
    this.setState({regUsername: e.target.value});
  }

  handleRegPasswordChange(e) {
    this.setState({regPassword: e.target.value});
  }

  handleRegPassword2Change(e) {
    this.setState({regPassword2: e.target.value});
  }

  showRegistration(val){
    this.props.dispatch(updateAuthenticationResponseMessage('Awaiting login/registration'));
    this.setState({toggleReg: val, password: ''});
  }

  render () {

    const navStyle = {
      backgroundColor: '#222222'
    }

    const navButtonStyle = {
      padding: "5px",
      margin: '5px',
      backgroundColor: '#4444FF',
      color: '#FFFFFF'
    }

    const navButtonStyleInActive = {
      padding: "5px",
      margin: '5px',
      backgroundColor: '#2222BB',
      color: '#FFFFFF'
    }

    const boxStyle = {
      color: '#FFFFFF',
      backgroundColor: '#222222',
      textAlign: "left",
      padding: "1em",
      margin: "1em",
      border: "2px solid #d3d3d3",
      verticalAlign: "middle",
      marginLeft: "auto",
      marginRight: "auto",
      marginTop: "100px"
    }

    const formStyle = {
      padding: '1em'
    }

    const labelStyle = {
      display: 'flex',
      fontWeight: 600
    }

    const buttonStyle = {
      justifyContent: 'flex-end'
    }

    const regPanel = (
      <Form style={formStyle}>

        <Col>
          <FormGroup>
            <Label>Username (In-game character name)</Label>
            <Input
              type="text"
              name="regUsername"
              id="regUsername"
              placeholder="username"
              value={this.state.regUsername}
              onChange={this.handleRegUsernameChange.bind(this)}
            />
          </FormGroup>
        </Col>

        <Col>
          <FormGroup>
            <Label for="examplePassword">Password</Label>
            <Input
              type="password"
              name="password"
              value={this.state.regPassword}
              id="examplePassword"
              placeholder="********"
              onChange={this.handleRegPasswordChange.bind(this)}
            />
          </FormGroup>
        </Col>

        <Col>
          <FormGroup>
            <Label for="examplePassword">Confirm password</Label>
            <Input
              type="password"
              name="password2"
              id="password2"
              value={this.state.regPassword2}
              placeholder="********"
              onChange={this.handleRegPassword2Change.bind(this)}
            />
          </FormGroup>
        </Col>

        <Button style={navButtonStyle} onClick={this.handleRegisterButtonClick.bind(this)} >Register</Button>
      </Form>
  );

  const loginPanel = (
      <Form style={formStyle}>
        <Col>
          <FormGroup>
            <Label>Username</Label>
            <Input
              type="username"
              name="username"
              id="username"
              placeholder="username"
              value={this.state.username || ''}
              onChange={this.handleUsernameChange.bind(this)}
            />
          </FormGroup>
        </Col>
        <Col>
          <FormGroup>
            <Label for="examplePassword">Password</Label>
            <Input
              type="password"
              name="password"
              id="examplePassword"
              placeholder="********"
              value={ this.state.password || '' }
              onChange={this.handlePasswordChange.bind(this)}
            />
          </FormGroup>
        </Col>
        <Button style={navButtonStyle} onClick={this.handleLoginButtonClick.bind(this)}>Login</Button>
      </Form>
    );

  const panelToShow = (this.state.toggleReg) ? regPanel : loginPanel;
  const loginButtonStyle = (this.state.toggleReg) ? navButtonStyle : navButtonStyleInActive;
  const regButtonStyle = (!this.state.toggleReg) ? navButtonStyle : navButtonStyleInActive;

  const notLoggedInPanel = (
  <div>
    <div style={navStyle}>
      <Button style={loginB