React: Render different component if authenticated at root - node.js

I'm trying to change the root of my app to render a different Router if the user authenticates. (public marketing website then the SAAS app when logged in).
My current code:
class MainRouter extends React.Component {
render() {
if(isAuthenticated()) {
return <AppRouter/>
} else {
return <FrontRouter/>
}
}
}
This works but I have to refresh the browser after authenticating or logging out otherwise it throws errors trying to render components that require a user.
isAuthenticated:
export const isAuthenticated = () => {
if(typeof window == 'undefined') {
return false
}
if(localStorage.getItem('jwt')) {
return JSON.parse(localStorage.getItem('jwt'))
} else {
return false;
}
};

Consider using state (ideally with hooks, or Redux if you are comfortable) to hold the JWT in memory. The idea is to store it immediately after logging in and have it available to use through component (or Redux) state. Then upon refreshing the page, state will still load from localState or cookies.
A secondary point, LocalStorage can act differently across different browsers, and even more so with privacy settings. Consider using cookies rather than localStorage as its usage is more predictable and consistent, plus the security risk is more or less the same. (code example below)
Third, I highly recommend switching from class-based components to function-based components to take advantage of React's new Hooks API. It is a much simpler way to manage state and saves you many lines of code.
// library imports
import React from 'react';
// component imports
import AppRouter from './AppRouter';
import FrontRouter from './FrontRouter';
// main
export default function MainRouter({isAuthenticated}) {
return isAuthenticated ? <AppRouter/> : <FrontRouter/>
}
If "isAuthenticated" is a boolean variable created by React's "useState" function from parent component, you can pass that variable to "MainRouter" component and conditionally render "AppRouter" or "FrontRouter" (I'm using a ternary operator instead of an "If/Else" statement to save lines)
In this case, the parent component file would look like this:
// in ParentComponent.jsx
// library imports
import React, {useState} from 'react';
// component imports
import MainRouter from './MainRouter';
// main component
export default function ParentComponent() {
// set state here
const defaultState = false;
const [isAuthenticated, setIsAuthenticated] =useState(defaultState);
return (
<div className="ParentComponent" >
<MainRouter isAuthenticated={isAuthenticated} />
</div>
)
}
helper function for getting cookies from w3schools (https://www.w3schools.com/js/js_cookies.asp)
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

Related

Loading data from API to file and then import it

I want to load some credentials from an API into a Node.js application, and then use them whenever necessary.
Currently there is a file that stores some information and accesses the credentials directly from the environment variables and exports everything. This file is then imported whenever necessary, something like this:
creds.js
module.exports = {
key: process.env.KEY || diqndwqn,
id: process.env.ID || dqw2231qzaxc,
db: {
user: dqdwmkovvoij,
pw: ofo9v8#$w
}
}
What I want is to do a call to an API from where I retrieve these values, where the values can still be imported after but the call is only made once at the start. A solution I can imagine is doing something like a singleton where you only do the API call the first time. I know I could also export a promise but I do not want to request the credentials several times, only one time when the server runs. Any clean alternatives?
You could make a simple class with a populate() function and getters and export it as a singleton.
class MyCreds {
constructor() {
this.key = null;
this.id = null;
this.db = { user: null, pw: null }
}
async populate() {
let creds = await whatever();
this.key = cred.key;
this.id = creds.id;
this.db.user = creds.user;
this.db.pw = creds.pw;
}
}
const myCreds = new MyCreds();
module.exports = myCreds;
Then at the very beginning of your process you populate with await require('my-creds').populate() and access everywhere else the same you currently are, with require('my-creds').id.

Can not reach API in async method of class

This is a node.js question.
I am trying to use the contentful API which is a CMS service.
This works:
const contentful = require("contentful");
const client = contentful.createClient({
space: SPACE_ID,
accessToken: ACCESS_TOKEN
})
client.getEntry("27xsh9l8AWraqFQwICeaCn").then((doc)=>{
console.log(doc);
});
However when I use the exact same code like this:
A.js
module.exports = class Contentful{
constructor(){
this.produceTemplate();
}
async produceTemplate() {
console.log("Fetching Contentful");
var doc = await client.getEntry("27xsh9l8AWraqFQwICeaCn")
console.log("done");
}
}
B.js
class Interpreter {
constructor(user) {
let contentful = new Contentful();
// This essentially waits until the contentful object successfully fetches the data and allocates it to a variable called RootTemplate
if(contentful.RootTemplate == null || contentful.RootTemplate == undefined){
console.log("Waiting for Contentful service..");
while(contentful.RootTemplate == null || contentful.RootTemplate == undefined){}
}
}
}
When I run the code output is
Fetching Contentful
So the promise
var doc = await client.getEntry("27xsh9l8AWraqFQwICeaCn")
never resolves. It gots stock.
Why is this happening? Is the class blocking me? or is there something wrong with the "contentful" library?
Note: I construct the Interpreter from my index.js, I did not put it because I think it is irrelevant
Note2: I create a client object in A.js with exact same space_ID and Access_Token. I cut them out while posting here in order to make code more readeble

How to mock API client created inside a test class?

I have a component class as below which create the rest and websocket connections using a third party npm module. I could change the Component.constructor to accept the module as a dependency so that I can inject a mock version during my Jest testing. But I read about Mocks with Jest, I thought I want to try it, but I cannot seem to understand how to intercept Api.Rest() and Api.Websocket return values.
// component.ts
import * as Api from 'npm-module'
import * as wait from 'wait-for-stuff' // actual npm module
export class Component {
private _rest:any;
private _websocket:any;
public events = new EventEmitter();
constructor() {
// I want to intecept the return value of
// Api.Rest() and Api.Websocket() to use mock versions.
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
// so that when do stuff with this._rest and this._websocket;
// I can control what is the expected results during test
this._websocket.onUpdate((data) => {
events.emit('update', data);
});
var value = wait.for.promise(this._rest.getSomething());
}
}
Do I have to use another test library like Sinon or Jasmine?
Here is a simplified working example to get you started:
// #ts-ignore
import * as Api from 'npm-module'; // <= (ts-ignore since "npm-module" doesn't exist)
import EventEmitter from 'events';
jest.mock('npm-module', () => {
const getSomethingMock = jest.fn(); // <= always return...
const onUpdateMock = jest.fn(); // <= ...the same mocks...
return {
Rest: () => ({ getSomething: getSomethingMock }),
Websocket: () => ({ onUpdate: onUpdateMock })
}
},
{ virtual: true }); // <= (use virtual since "npm-module" doesn't exist)
class Component {
private _rest: any;
private _websocket: any;
public events = new EventEmitter();
constructor() {
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
this._websocket.onUpdate((data) => { // <= ...so that this onUpdate...
this.events.emit('update', data);
});
}
}
test('Component', () => {
const component = new Component();
const listener = jest.fn();
component.events.on('update', listener);
const onUpdate = new Api.Websocket().onUpdate; // <= ...is the same as this one
const onUpdateArrowFunction = onUpdate.mock.calls[0][0]; // <= get the arrow function passed to it
onUpdateArrowFunction('mock data'); // <= now call the function
expect(listener).toHaveBeenCalledWith('mock data'); // Success!
});
Details
Jest takes over the require system and allows you to specify what you want it to return when a module is required (note that TypeScript import statements get compiled to require calls).
One way to mock a module is to create a manual mock by creating a file at __mocks__/npm-module.ts that contains your mock.
Another way (show above) is to use jest.mock and pass it a module factory function.
Whenever the module is required during the test Jest will return the mocked module instead.
Note that the example above always returns the same mock for getSomething and onUpdate so those mock functions can be retrieved during the test.
Also note the use of mockFn.mock.calls to retrieve this arrow function:
(data) => {
this.events.emit('update', data);
}
...which gets passed to onUpdate. Once it has been retrieved, it can be called directly which triggers the listener as expected.

Calling Set on a Firebase Database Reference Causes the Webpage to Hang

I'm using React and Node to build an web-based interface to modify data in a Firebase database. I've previously used the Firebase Web SDK in this app to load data from the database, but I've encountered a strange issue with saving a user's changes. When I call set on a database reference (i.e. firebase.database().ref('/path/to/object').set({abc: 'xyz'})), the webpage hangs. Oddly enough, the changes are saved to the database, but the callbacks specified with then are never called (depending on the browser, a This page is slowing down your browser message appears). I'm certain that the issue is related to set as removing the call removes the hang (see save() in my code below).
import React from 'react'
import * as firebase from 'firebase'
// additional (unrelated) imports
export default class Editor extends React.Component {
constructor(props) {
super(props)
this.state = {
savingModal: false,
errorModal: false,
cancelModal: false,
errors: []
}
}
save() {
// this.form is a Reactstrap Form
// validate is a function that returns an array of strings
var errors;
// validate the form, show the errors if any
if ((errors = this.form.validate()) && errors.length > 0)
this.setState({errorModal: true, errors: errors})
else {
// collect is a function that returns an object with the data that the user entered
var x = this.form.collect()
// getEditorInfo is a function that returns info such as the type of object being edited
var info = this.getEditorInfo()
firebase.database().ref(`/${info.category}/${x.id}/`).set(x).then(() => {
this.closeEditor()
}, e => {
alert(`An unexpected error occurred:\n${e}`)
})
this.setState({savingModal: true})
}
}
// closes the window or redirects to /
closeEditor() {
if (window.opener)
window.close()
else
window.location.href = '/'
}
render() {
// BasicModal is a custom component that renders a Reactstrap Modal
// IndeterminateModal is a custom component that renders a Reactstrap Modal with only a Progress element
// EditorToolbar and EditorForm are custom components that render the UI of the page (I don't think they're relevant to the issue)
var info = this.getEditorInfo()
if (!info)
return <BasicModal isOpen={true} onPrimary={this.closeEditor} primary="Close" header="Invalid Request" body="ERROR: The request is invalid."/>
else
return <div>
<EditorToolbar onSave={this.save.bind(this)} onCancel={() => this.setState({cancelModal: true})}/>
<EditorForm ref={f => this.form = f}/>
<BasicModal toggle={() => this.setState({cancelModal: !this.state.cancelModal})} isOpen={this.state.cancelModal} header="Unsaved Changes" body={<p>If you close this window, your changes will not be saved.<br/>Are you sure you want to continue?</p>} primary="Close Anyway" primaryColor="danger" secondary="Go Back" onPrimary={this.closeEditor}/>
<IndeterminateModal style={{
top: '50%',
transform: 'translateY(-50%)'
}} isOpen={this.state.savingModal} progressColor="primary" progressText="Processing..."/>
<BasicModal toggle={() => this.setState({errorModal: false, errors: []})} isOpen={this.state.errorModal} header="Validation Error" body={<div><p>Please resolve the following errors:<br/></p><ul>{(this.state.errors || []).map(e => <li key={e}>{e}</li>)}</ul></div>} primary="Dismiss" primaryColor="primary"/>
</div>
}
}
UPDATE 1/8/2018
I came across this article today and I decided to try a new solution involving JavaScript's setTimeout method. In my situation, the freeze occurred after calling this.setState in my app then calling firebase.database().ref(path).set(data). I suspect the freezing issue was caused by this. I guess JavaScript couldn't handle the state change and Firebase operation all at once. This new solution is functional, more secure, faster, and simpler. Take a look:
// to perform your desired Firebase operation:
var timeout = 50 // give JS some time (e.g. 50ms) to finish other operations first
setTimeout(() => firebase.database().ref(path).set(data).then(
() => {/* ... */},
error => {/* ... */}),
timeout)
OLD SOLUTION
I ended up finding a solution. I'm sure it could be improved, but it works. I used the Web Workers API to run my Firebase code.
Create a new JavaScript file in your public folder (Node.js)
Download a copy of the Firebase web SDK source and place it in public
I chose to communicate with my Worker with postMessage
FirebaseWorker.js
self.onmessage = event => {
importScripts('./firebase.js') // import the local Firebase script
firebase.initializeApp({/* your config */})
const promise = p => p.then(
() => self.postMessage({error: null}),
e => self.postMessage({error: e})
const doWork = () => {
switch (event.data.action) {
case 'get':
promise(firebase.database().ref(event.data.path).once('value'))
break;
case 'set':
promise(firebase.database().ref(event.data.path).set(event.data.data))
break;
case 'update':
promise(firebase.database().ref(event.data.path).update(event.data.data))
break;
case 'remove':
promise(firebase.database().ref(event.data.path).remove())
break;
}
}
if (!firebase.auth().currentUser)
firebase.auth().signInWithEmailAndPassword(event.data.email, event.data.password).then(() => doWork())
else
doWork()
}
To use the Worker:
var worker = new Worker('FirebaseWorker.js')
worker.onmessage = event => {
if (event.data.error)
alert(event.data.error)
// ...
}
worker.postMessage({
data: {/* your data (required if set or update is used) */},
path: '/path/to/reference',
action: 'get, set, update, or remove',
email: 'someone#example.com',
password: 'password123'
})

Maintaining es6 class state over requests with express router

I recently tried updating my project code structure with es6 classes in nodejs.
Here's what a controller class look like
class TaskController {
constructor(io, redisClient) {
this.socket = io;
this.redisClient = redisClient;
}
create(req, res) {
let taskDetails = req.body;
taskDetails.owner = req.user.id;
let errorFields = Validation.validate(taskDetails, [
{ name: 'title', type: 'text' }
]);
if (errorFields.length > 0) {
return ErrorHandler.handleError(res, errorFields);
}
...`
}
}
and Here's how I'm initializing the class in a routes file
module.exports = (app, io, redisClient) => {
let taskController = new TaskController(io, redisClient);
router.post('', middlewares.isAuthorized('task:create'), taskController.create);
app.use('/api/tasks', middlewares.isAuthenticated(), router);
};
The problem is while hitting the create api the this object is undefined in the create function.
With a little debugging, I know that the constructor is called when registering the routes.
But the taskController looses context when the create function is actually called.
How can I make it work with this structure or do I have to revert back to
module.exporting every function task controller?
You can use bind method to bind context. while passing create function.
router.post('', middlewares.isAuthorized('task:create'), taskController.create.bind(taskController));
create method is losing context because it is getting called in different scope by post method.
You can bind context using bind method which returns a function and pass that function to create method.

Resources