React history & universal-cookie not working in Electron App

Viewed 221

I'm currently carrying over a web project over to Electron, with the use of ReactJs, Redux, Express, MongoDB & Tailwind. I'm currently stuck on my authentication, where previously I made use of universal-cookies and history.push() for the usual redirecting and validation of cookies for the authentication process. Everything works perfectly fine, when I'm in the development server. I can see the cookies being added and redirection is working as expected. Now when I build the application, I try to login in, but nothing happens at all, but I can see the login request being successfully sent from the express server. I'm unsure if cookies are supported in Electron or either the use of history.push() (react-router-dom), but it's just odd seeing it work in the development server. Any help would be appreciated.

Login.js

import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import Cookies from 'universal-cookie'
import { useDispatch } from 'react-redux'
import login from '../../redux/actions/auth/auth-login'

function Login({history}) {
    const cookies = new Cookies();
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const dispatch = useDispatch();

    useEffect(() => {
        if (!cookies.get("authToken")) {
            history.push("/login");
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [history]);

    

    const loginHandler = () => {
        dispatch(login(email, password)).then(() => {
            history.push('/hub/calendar')
        });
    };

    return (
        <div>
            <div style={{paddingLeft: '42%', paddingTop: '10%'}}>
                <div className="w-80">
                    <img className="mb-4" src="https://res.cloudinary.com/my-studios-pro-primary/image/upload/v1639303304/LogoMakr-8hg510_rtlypr.png" alt=""/>
                    <input value={email} onChange={(e) => setEmail(e.target.value)} type="text" placeholder="Enter email" className="mt-6 shadow-md bg-grey-100 rounded-xl h-10 pl-2 w-80 outline-none"/>
                    <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="Enter password" className="mt-2 shadow-md bg-grey-100 rounded-xl h-10 pl-2 w-80 outline-none"/>
                    <p className="mt-6 text-gray-400">Have you <Link to="/forgotpassword"><mark style={{backgroundColor: 'transparent'}} className="cursor-pointer transition duration-300 ease-in-out hover:text-grey-700 font-bold text-gray-500">forgotten your password?</mark></Link></p>
                    <p onClick={loginHandler} className="cursor-pointer transition duration-300 ease-in-out hover:bg-primarygreen-600 text-center outline-none bg-primarygreen-500 pl-4 pr-4 pt-2 pb-2 rounded-xl w-full text-white-100 font-medium text-xl float-right mt-14">Login</p>
                </div>
            </div>
        </div>
    )
}

export default Login

Redux - login action

import * as actionTypes from '../action-types';
import axios from "axios";
import Cookies from 'universal-cookie';

export const Login = (email, password) => async (dispatch) => {
    const cookies = new Cookies();
    
    const config = {
        headers: {
            "Content-Type": "application/json"
        },
    };

    try {
        dispatch({type: actionTypes.USER_LOGIN_REQUEST});  
        const { data } = await axios.post('/api/auth/login', {email, password}, config);
        dispatch({type: actionTypes.USER_LOGIN_SUCCESS, payload: data});
        localStorage.setItem('userInfo', JSON.stringify(data));
        cookies.set('authToken', data.token);
    } catch (error) {
        dispatch({
            type: actionTypes.USER_LOGIN_FAIL,
            payload: error.response && error.response.data.message 
            ? error.response.data.message : error.message
        });
    }
}



// eslint-disable-next-line import/no-anonymous-default-export
export default Login

PrivateRoute

import React from 'react';
import {Redirect, Route} from 'react-router-dom';
import Cookies from 'universal-cookie';

const PrivateRoute = ({component: Component, ...rest}) => {
    const cookies = new Cookies();
    return (
        <Route 
            {...rest}

            render = {
                (props) => 
                cookies.get("authToken") ? (
                        <Component {...props} />
                    ) : (
                        <Redirect to="/login"/>
                    )
            }
        />
    )
}

export default PrivateRoute

App.js

import React, { Component } from 'react';
import './App.css';
import { channels } from '../shared/constants';
import {HashRouter as Router, Route, Switch} from 'react-router-dom';
import PrivateRoute from '../routes/privateRoute';
import Forgotpassword from '../screens/auth/forgotpassword';
import Reviews from '../screens/hub/reviews';
import Journal from '../screens/hub/journal';
import Login from '../screens/auth/login';
import Hub from '../screens/hub/calendar';
import Test from '../screens/test';
const { ipcRenderer } = window; 

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      appName: '',
      appVersion: '',
    }
    ipcRenderer.send(channels.APP_INFO);
    ipcRenderer.on(channels.APP_INFO, (event, arg) => {
      ipcRenderer.removeAllListeners(channels.APP_INFO);
      const { appName, appVersion } = arg;
      this.setState({ appName, appVersion });
    });
  }

  render() {
    return (
      <Router>
        <div className="font-poppins">
          <Switch>
            <PrivateRoute exact path="/" component={Test}/>
            <PrivateRoute exact path="/hub/calendar" component={Hub}/>
            <PrivateRoute exact path="/hub/reviews" component={Reviews} />
            <PrivateRoute exact path="/hub/journal" component={Journal} />
            
            <Route exact path="/login" component={Login} />
            <Route exact path="/forgotpassword" component={Forgotpassword} />
          </Switch>
        </div>
      </Router>
    );
  }
}

export default App;

Electron (main.js)

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const url = require('url');
const { channels } = require('../src/shared/constants');

let mainWindow;

function createWindow () {
  const startUrl = process.env.ELECTRON_START_URL || url.format({
    pathname: path.join(__dirname, '../index.html'),
    protocol: 'file:',
    slashes: true,
  });
  mainWindow = new BrowserWindow({
    width: 1500,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });
  mainWindow.loadURL(startUrl);
  mainWindow.on('closed', function () {
    mainWindow = null;
  });
}

app.on('ready', createWindow);

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow();
  }
});

ipcMain.on(channels.APP_INFO, (event) => {
  event.sender.send(channels.APP_INFO, { 
    appName: app.getName(),
    appVersion: app.getVersion(),
  });
});

Preload

const { ipcRenderer } = require('electron');
window.ipcRenderer = ipcRenderer;

Package.json

  "scripts": {
    "start": "export BROWSER=none && craco start",
    "start-win": "set BROWSER=none && craco start",
    "start-electron": "export ELECTRON_START_URL=http://localhost:3000 && electron .",
    "start-electron-win": "set ELECTRON_START_URL=http://localhost:3000 && electron .",
    "clean": "rm -rf build dist",
    "clean-win": "rmdir build /S /Q & rmdir dist/S /Q",
    "build": "craco build",
    "build-electron": "mkdir build/src && cp -r electron/. build/electron && cp -r src/shared/. build/src/shared",
    "build-electron-win": "mkdir build/src && robocopy electron build/electron /S & robocopy src/shared build/src/shared /S",
    "package": "electron-builder build --mac --win -c.extraMetadata.main=build/electron/main.js --publish never",
    "package-win": "electron-builder build --win -c.extraMetadata.main=build/electron/main.js --publish never",
    "test": "craco test",
    "eject": "react-scripts eject"
  },
  "build": {
    "files": [
      "build/**/*",
      "node_modules/**/*"
    ],
    "publish": {
      "provider": "github",
      "repo": "mps",
      "owner": "marlborojamez"
    }
  },
1 Answers

Well I've just realised, coding long periods of time can cause a person to make the simplest mistakes. Well I figured that in order for the electron app to run after the build, the client server is still needed... so the only think that had to be done was to make use write a simple script, to ensure the backend is executed as well as executing the front-end server in order for the actual build to work.

Related