Commit 56fbbca8 authored by Valeriy Panov's avatar Valeriy Panov Committed by Matija Obreza
Browse files

React server side rendering

parent 448483b2
Pipeline #1535 canceled with stages
in 2 minutes and 59 seconds
......@@ -2,7 +2,7 @@ import axios from 'axios';
import {LOGIN_URL, LOGOUT_URL, CHECK_TOKEN_URL, VERIFY_GOOGLE_TOKEN_URL} from '../constants/apiURLS';
import * as Constants from '../constants/login';
import {addTokenInterceptor} from '../utilities/axiosConfig';
import {clearData, storeData} from '../utilities/index';
import {clearCookies, saveCookies} from '../utilities/index';
import {ROLE_CLIENT} from '../constants/userRoles';
function loginRequest(username, password) {
......@@ -17,7 +17,7 @@ function loginRequest(username, password) {
grant_type: 'password',
},
}).then(({data}) => {
storeData(data);
saveCookies(data);
addTokenInterceptor(data.access_token);
return dispatch(loginUser(data));
});
......@@ -31,8 +31,6 @@ function loginAppRequest() {
grant_type: 'client_credentials',
},
}).then(({data}) => {
storeData(data);
addTokenInterceptor(data.access_token);
return dispatch(loginApp(data));
});
};
......@@ -45,7 +43,7 @@ function logoutRequest(token) {
token,
},
}).then(() => {
clearData();
clearCookies();
return dispatch(logout());
});
};
......@@ -58,8 +56,6 @@ function checkTokenRequest(token) {
token,
},
}).then(({data}) => {
storeData(data);
addTokenInterceptor(token);
return dispatch(checkToken({access_token: token, ...data}));
});
};
......@@ -73,7 +69,7 @@ function verifyGoogleTokenRequest(accessToken, googleClientId) {
googleClientId,
},
}).then(({data}) => {
storeData(data);
saveCookies(data);
addTokenInterceptor(data.access_token);
return dispatch(loginApp(data));
});
......
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as cookies from 'es-cookie';
import Header from './header';
import {checkTokenRequest, logoutRequest, loginAppRequest, loginUser} from '../actions/login';
import {clearData} from '../utilities/index';
import {clearCookies, saveCookies} from '../utilities/index';
import {addTokenInterceptor} from '../utilities/axiosConfig';
const ssr: boolean = process.env.SSR === 'true';
const token: string = typeof window !== 'undefined' && cookies.get('access_token') ? JSON.parse(cookies.get('access_token')) : null;
interface IAppProps extends React.ClassAttributes<any> {
children: any;
......@@ -25,31 +30,41 @@ class App extends React.Component<IAppProps, any> {
this.state = {
checked: false,
};
if (ssr) {
addTokenInterceptor(token);
}
}
public componentDidMount() {
const token = window.localStorage.getItem('access_token');
if (token) {
this.props.checkTokenRequest(token).then(() => {
this.setState({checked: true});
}).catch(() => {
clearData();
this.props.router.push('/r/login');
this.props.loginAppRequest().then(() => {
if (!ssr) {
if (token) {
this.props.checkTokenRequest(token).then((data) => {
addTokenInterceptor(data.access_token);
this.setState({checked: true});
}).catch(() => {
clearCookies();
this.props.router.push('/r/login');
this.props.loginAppRequest().then((data) => {
saveCookies(data);
addTokenInterceptor(data.access_token);
this.setState({checked: true});
});
});
} else {
this.props.loginAppRequest().then((data) => {
saveCookies(data);
addTokenInterceptor(data.access_token);
this.setState({checked: true});
});
});
} else {
this.props.loginAppRequest().then(() => {
this.setState({checked: true});
});
}
}
}
public render() {
const {children, login, logoutRequest, loginAppRequest, router} = this.props;
return (
this.state.checked ?
(this.state.checked || ssr) ?
(
<div>
<Header login={ login } logoutRequest={ logoutRequest }
......
import {checkTokenRequest, loginAppRequest} from '../actions/login';
function checkAuthTokenRequest(req, dispatch) {
const token = req.cookies.access_token;
if (token) {
return dispatch(checkTokenRequest(token))
.catch(() => {
return dispatch(loginAppRequest());
});
} else {
return dispatch(loginAppRequest());
}
}
export default function checkAuthToken(req, res, dispatch) {
return checkAuthTokenRequest(req, dispatch)
.then((data) => {
res.cookie('authorities', JSON.stringify(data.authorities), { path: '/' });
res.cookie('access_token', JSON.stringify(data.access_token), { path: '/' });
return data;
});
}
import * as cookies from 'es-cookie';
import {CHECK_TOKEN, LOGIN_USER, LOGIN_APP, LOGOUT, VERIFY_GOOGLE_TOKEN} from '../constants/login';
import {ROLE_CLIENT} from '../constants/userRoles';
let authorities = [ROLE_CLIENT];
if (typeof window !== 'undefined' && window.localStorage.getItem('authorities')) {
authorities = window.localStorage.getItem('authorities').split(',');
if (typeof window !== 'undefined' && cookies.get('authorities')) {
authorities = JSON.parse(cookies.get('authorities'));
}
const INITIAL_STATE = {
......
......@@ -63,11 +63,13 @@ export function arraysEqual(a: any[], b: any[]): boolean {
}
}
export function storeData(resp) {
export function saveCookies(resp) {
const cookies = require('es-cookie');
Object.keys(resp).filter((key) => ['authorities', 'access_token'].indexOf(key) > -1)
.forEach((key) => window.localStorage.setItem(key, resp[key]));
.forEach((key) => cookies.set(key, JSON.stringify(resp[key]), { path: '/' }));
}
export function clearData() {
['authorities', 'access_token'].forEach((key) => window.localStorage.removeItem(key));
export function clearCookies() {
const cookies = require('es-cookie');
['authorities', 'access_token'].forEach((key) => cookies.remove(key));
}
......@@ -13,6 +13,7 @@ const HOST = process.env.HOST;
const PORT = process.env.PORT;
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const CATALOG_API_URL = process.env.CATALOG_API_URL;
const SSR = process.env.SSR;
const sortedChunks = function(list) {
return function(chunk1, chunk2) {
......@@ -187,7 +188,8 @@ module.exports = {
new webpack.DefinePlugin({
'process.env': {
GOOGLE_CLIENT_ID: JSON.stringify(GOOGLE_CLIENT_ID)
GOOGLE_CLIENT_ID: JSON.stringify(GOOGLE_CLIENT_ID),
SSR: JSON.stringify(SSR)
}
}),
......
......@@ -7,6 +7,7 @@ services:
- "CLIENT_ID=${CATALOG_CLIENT_ID}"
- "CLIENT_SECRET=${CATALOG_CLIENT_SECRET}"
- "CATALOG_API_URL=https://catalog-master.review.genesys-pgr.org"
- "SSR=true"
networks:
- traefik-net
deploy:
......
......@@ -33,6 +33,11 @@ gulp.task('clean:target', function(cb) {
del([path.join(__dirname, 'target')], cb);
});
gulp.task('clean:index_html', function(cb) {
// You can use multiple globbing patterns as you would with `gulp.src`
del([path.join(__dirname, 'target/www/index.html')], cb);
});
gulp.task('copy:package_json', () => {
return gulp.src([
'package.json'
......
......@@ -21,11 +21,12 @@
"toolbox": "react-toolbox-themr",
"clean": "gulp clean",
"build": "webpack --config config/webpack-development.config.js",
"build:production": "cross-env NODE_ENV=production webpack --config config/webpack-production.config.js",
"build:production": "cross-env NODE_ENV=production SSR=true webpack --config config/webpack-production.config.js",
"postbuild:production": "gulp clean:index_html",
"build:ssr": "gulp clean:all; gulp build:prod; npm run build:production",
"server": "webpack-dev-server --config config/webpack-development.config.js",
"server:production": "cross-env NODE_ENV=production webpack-dev-server --config config/webpack-production.config.js",
"server:ssr": "gulp clean:all; gulp build:ssr; npm run build; node target/www/index.js"
"server:ssr": "gulp clean:all; gulp build:ssr; cross-env SSR=true npm run build; cross-env SSR=true node target/www/index.js"
},
"reactToolbox": {
"output": "src/main/react/assets"
......@@ -34,7 +35,10 @@
"axios": "^0.16.1",
"babel-runtime": "^6.23.0",
"classnames": "^2.2.5",
"cookie-parser": "^1.4.3",
"es-cookie": "^1.1.1",
"es6-promise": "^4.1.0",
"express": "^4.15.2",
"express-http-proxy": "^1.0.3",
"flexboxgrid": "^6.3.1",
"history": "^4.6.1",
......@@ -42,8 +46,10 @@
"isomorphic-fetch": "^2.2.1",
"material-design-icons": "^3.0.1",
"react": "~15.4.2",
"react-bootstrap": "^0.30.10",
"react-css-modules": "^4.1.0",
"react-dom": "~15.4.2",
"react-fontawesome": "^1.6.1",
"react-google-login": "^2.9.2",
"react-markdown": "^2.4.6",
"react-redux": "^5.0.3",
......@@ -56,10 +62,7 @@
"redux-logger": "^3.0.0",
"redux-thunk": "^2.2.0",
"roboto-fontface": "^0.7.0",
"serialize-javascript": "^1.3.0",
"express": "^4.15.2",
"react-bootstrap": "^0.30.10",
"react-fontawesome": "^1.6.1"
"serialize-javascript": "^1.3.0"
},
"devDependencies": {
"@types/base16": "^1.0.1",
......
......@@ -20,7 +20,9 @@ import 'path';
import * as path from 'path';
import * as fs from 'fs';
import thunk from 'redux-thunk';
import * as cookieParser from 'cookie-parser';
import config from './config/config';
import checkAuthToken from './assets/middleware/checkAuthToken';
const app = express();
......@@ -49,6 +51,7 @@ app.use('/proxy', proxy(config.apiUrl, {
}));
app.use(express.static(path.join(__dirname)));
app.use(cookieParser());
app.use((req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
......@@ -83,7 +86,8 @@ app.use((req, res) => {
.replace('SERVER_RENDERED_STATE', serialize(initialState, {isJSON: true}));
}
fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
checkAuthToken(req, res, store.dispatch)
.then(() => fetchComponentData(store.dispatch, renderProps.components, renderProps.params))
.then(renderView)
.then((template) => res.end(template))
.catch((err) => res.end(err.message));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment