/* eslint-disable no-underscore-dangle */
import path from 'path';
import express, { NextFunction, Request, Response } from 'express';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import expressJwt, { UnauthorizedError as Jwt401Error } from 'express-jwt';
import jwt from 'jsonwebtoken';
import React from 'react';
import ReactDOM from 'react-dom/server';
import PrettyError from 'pretty-error';
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express';
import { getDataFromTree } from 'react-apollo';
import createApolloClient from '@config@/createApolloClient.server';
import App from '@config@/App';
import Html from '@config@/Html';
import { ErrorPageWithoutStyle } from '@config@/error-page/ErrorPage';
import errorPageStyle from '@config@/error-page/ErrorPage.css';
import passport from '@config@/passport';
import config from '@config@/config';
import createCache from '@config@/createCache';
import router from './router';
// import models from './data/models';
import schema from './schema';
// import assets from './asset-manifest.json'; // eslint-disable-line import/no-unresolved
// @ts-ignore
import chunks from './chunk-manifest.json'; // eslint-disable-line import/no-unresolved
import { AppContextTypes } from './AppContext';
import {
clientDefaults,
clientResolvers,
clientTypeDefs,
} from './clientSchema';
import serverRootValueDeps from '../../__generated__/serverRootValueDeps';
const serverRootValue = serverRootValueDeps[0]
? serverRootValueDeps[0][0].default
: {};
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at:', p, 'reason:', reason);
// send entire app down. Process manager will restart it
process.exit(1);
});
//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
// @ts-ignore
global.navigator = global.navigator || {};
// @ts-ignore
global.navigator.userAgent = global.navigator.userAgent || 'all';
const app = express();
//
// If you are using proxy from external machine, you can set TRUST_PROXY env
// Default is to trust proxy headers only from loopback interface.
// -----------------------------------------------------------------------------
app.set('trust proxy', config.trustProxy);
//
// Register Node.js middleware
// -----------------------------------------------------------------------------
// @ts-ignore
app.use(express.static(path.resolve(__userDir__, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//
// Authentication
// -----------------------------------------------------------------------------
app.use(
expressJwt({
secret: config.auth.jwt.secret,
credentialsRequired: false,
getToken: req => req.cookies.id_token,
}),
);
// Error handler for express-jwt
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
// eslint-disable-line no-unused-vars
if (err instanceof Jwt401Error) {
console.error('[express-jwt-error]', req.cookies.id_token);
// `clearCookie`, otherwise user can't use web-app until cookie expires
res.clearCookie('id_token');
}
next(err);
});
app.use(passport.initialize());
app.get(
'/login/facebook',
passport.authenticate('facebook', {
scope: ['email', 'user_location'],
session: false,
}),
);
app.get(
'/login/facebook/return',
passport.authenticate('facebook', {
failureRedirect: '/login',
session: false,
}),
(req, res) => {
const expiresIn = 60 * 60 * 24 * 180; // 180 days
const token = jwt.sign(req.user, config.auth.jwt.secret, { expiresIn });
res.cookie('id_token', token, { maxAge: 1000 * expiresIn, httpOnly: true });
res.redirect('/');
},
);
//
// Register API middleware
// -----------------------------------------------------------------------------
// https://github.com/graphql/express-graphql#options
const server = new ApolloServer({
schema: makeExecutableSchema(schema),
uploads: false,
introspection: __DEV__,
playground: __DEV__,
debug: __DEV__,
context: ({ req }: { req: Request }) => ({ req }),
rootValue: serverRootValue,
});
server.applyMiddleware({ app });
//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
try {
const css = new Set();
// Enables critical path CSS rendering
// https://github.com/kriasoft/isomorphic-style-loader
const insertCss = (...styles: any[]) => {
styles.forEach(style => css.add(style._getCss()));
};
const apolloClient = createApolloClient({
schemaArgs: {
schema: makeExecutableSchema(schema),
// This is a context consumed in GraphQL Resolvers
context: { req },
},
partialCacheDefaults: {
user: req.user || null,
},
apolloCache: createCache(),
clientDefaults,
clientResolvers,
clientTypeDefs,
});
// Global (context) variables that can be easily accessed from any React component
// https://facebook.github.io/react/docs/context.html
const context: AppContextTypes = {
// The twins below are wild, be careful!
pathname: req.path,
query: req.query,
};
const route = await router.resolve(context);
context.params = route.params;
if (route.redirect) {
res.redirect(route.status || 302, route.redirect);
return;
}
const data = { ...route };
const rootComponent = (