text: /**
   * React Starter Kit (https://www.reactstarterkit.com/)
   *
   * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE.txt file in the root directory of this source tree.
   */
  
  import path from 'path'
  import Promise from 'bluebird'
  import express from 'express'
  import proxyMiddleware from 'http-proxy-middleware'
  import cookieParser from 'cookie-parser'
  import bodyParser from 'body-parser'
  import expressJwt, { UnauthorizedError as Jwt401Error } from 'express-jwt'
  import { graphql } from 'graphql'
  import expressGraphQL from 'express-graphql'
  import jwt from 'jsonwebtoken'
  import nodeFetch from 'node-fetch'
  import React from 'react'
  import ReactDOM from 'react-dom/server'
  import { getDataFromTree } from 'react-apollo'
  import PrettyError from 'pretty-error'
  import apolloFetch from './data/graphql/apollo-fetch'
  import createApolloClient from './core/createApolloClient'
  import App from './components/App'
  import Html from './components/Html'
  import { ErrorPageWithoutStyle } from './routes/error/ErrorPage'
  import errorPageStyle from './routes/error/ErrorPage.css'
  import createFetch from './createFetch'
  import router from './router'
  import schema from './data/schema'
  // import assets from './asset-manifest.json'; // eslint-disable-line import/no-unresolved
  import chunks from './chunk-manifest.json' // eslint-disable-line import/no-unresolved
  import configureStore from './store/configureStore'
  import { setRuntimeVariable } from './actions/runtime'
  import config from './config'
  import ACCOUNT_INFO from './gql/account/ACCOUNT_INFO.gql'
  import { setUserInfo } from './actions/user'
  
  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.
  // -----------------------------------------------------------------------------
  global.navigator = global.navigator || {}
  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
  // -----------------------------------------------------------------------------
  app.use(express.static(path.resolve(__dirname, 'public')))
  app.use(cookieParser())
  app.use(bodyParser.urlencoded({ extended: true }))
  app.use(bodyParser.json())
  // proxy-middleware
  app.use('/api', proxyMiddleware(config.proxy))
  //
  // Authentication
  // -----------------------------------------------------------------------------
  app.use(
    expressJwt({
      secret: config.auth.jwt.secret,
      // set to true if front-end authenticate required
      credentialsRequired: false,
      getToken: req => req.cookies.token
    }).unless({ path: ['/login', '/loginapi', '/graphql'] })
  )
  // Error handler for express-jwt
  app.use((err, req, res, next) => {
    // eslint-disable-line no-unused-vars
    if (err instanceof Jwt401Error) {
      console.error('[express-jwt-error]', req.cookies.token)
      // `clearCookie`, otherwise user can't use web-app until cookie expires
      res.clearCookie('token')
      res.redirect('/login')
    }
    next(err)
  })
  
  app.post('/loginapi', (req, res) => {
    const payload = req.body
    // if (payload.userName !== 'zuomeng') {
    //   res.clearCookie('token')
    //   return res.json({
    //     errorCode: 1
    //   })
    // }
    const expiresIn = 60 * 60 * 24 * 180
    const token = jwt.sign(
      { username: payload.userName },
      config.auth.jwt.secret,
      {
        expiresIn
      }
    )
    res.cookie('token', token, { maxAge: 1000 * expiresIn, httpOnly: false })
    return res.json({
      errorCode: 0,
      userName: 'zuomeng'
    })
  })
  //
  // Register API middleware
  // -----------------------------------------------------------------------------
  // https://github.com/graphql/express-graphql#options
  const graphqlMiddleware = expressGraphQL(req => ({
    schema,
    graphiql: __DEV__,
    rootValue: { request: req },
    pretty: __DEV__
  }))
  
  app.use('/graphql', graphqlMiddleware)
  
  //
  // 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) => {
        // eslint-disable-next-line no-underscore-dangle
        styles.forEach(style => css.add(style._getCss()))
      }
  
      const apolloClient = createApolloClient({
        schema,
        rootValue: { request: req }
      })
  
      // Universal HTTP client
      const fetch = createFetch(nodeFetch, {
        baseUrl: config.api.serverUrl,
        cookie: req.headers.cookie,
        apolloClient,
        schema,
        graphql
      })
  
      const initialState = {
        user: {}
      }
  
      const store = configureStore(initialState, {
        cookie: req.headers.cookie,
        fetch,
        // I should not use `history` on server.. but how I do redirection? follow universal-router
        history: null
      })
      // 提前抓去用户信息，判断登录状态
      const cookie = req.cookies
      const pathname = req.path
      const whiteList = ['/login', '/register']
      // 如果没有token
      if (!cookie.token) {
        // 如果不是去白名单页面，重定向到登陆页
        if (whiteList.indexOf(pathname) < 0) {
          return res.redirect('/login')
        }
        // 如果有token并且要去注册和登陆页
      } else if (pathname === '/login' || pathname === '/register') {
        return res.redirect('/')
      }
      const { data: accountData } = await apolloFetch({
        query: ACCOUNT_INFO
      })
      // 提取结果中的用户信息
      const info = accountData.account
      store.dispatch(setUserInfo(info))
      store.dispatch(
        setRuntimeVariable({
          name: 'initialNow',
          value: new Date().toString()
        })
      )
      // Global (context) variables that can be easily accessed from any React component
      // https://facebook.github.io/react/docs/context.html
      const context = {
        insertCss,
        fetch,
        // The twins below are wild, be careful!
        pathname: req.path,
        query: req.query,
        // You can access redux through react-redux connect
        store,
        storeSubscription: null,
        // Apollo Client for use with react-apollo
        client: apolloClient,
        cookie: req.cookies
      }
  
      const route = await router.resolve(context)
  
      if (route.redirect) {
        return res.redirect(route.status || 302, route.redirect)
      }
  
      const data = { ...route }
      const rootComponent = <App context={context}>{route.component}</App>
      await getDataFromTree(rootComponent)
      // this is here because of Apollo redux APOLLO_QUERY_STOP action
      await Promise.delay(0)
      data.children = await ReactDOM.renderToString(rootComponent)
      data.styles = [{ id: 'css', cssText: [...css].join('') }]
  
      const scripts = new Set()
      const addChunk = chunk => {
        if (chunks[chunk]) {
          chunks[chunk].forEach(asset => scripts.add(asset))
        } else if (__DEV__) {
          throw new Error(`Chunk with name '${chunk}' cannot be found`)
        }
      }
      addChunk('client')
      if (route.chunk) addChunk(route.chunk)
      if (route.chunks) route.chunks.forEach(addChunk)
      data.scripts = Array.from(scripts)
  
      // Furthermore invoked actions will be ignored, client will not receive them!
      if (__DEV__) {
        // eslint-disable-next-line no-console
        console.log('Serializing store...')
      }
      data.app = {
        apiUrl: config.api.clientUrl,
        state: context.store.getState(),
        apolloState: context.client.extract()
      }
  
      const html = ReactDOM.renderToStaticMarkup(<Html {...data} />)
      res.status(route.status || 200)
      res.send(`<!doctype html>${html}`)
    } catch (err) {
      next(err)
    }
  })
  
  //
  // Error handling
  // -----------------------------------------------------------------------------
  const pe = new PrettyError()
  pe.skipNodeFiles()
  pe.skipPackage('express')
  
  // eslint-disable-next-line no-unused-vars
  app.use((err, req, res, next) => {
    console.error(pe.render(err))
    const html = ReactDOM.renderToStaticMarkup(
      <Html
        title="Internal Server Error"
        description={err.message}
        styles={[{ id: 'css', cssText: errorPageStyle._getCss() }]} // eslint-disable-line no-underscore-dangle
      >
        {ReactDOM.renderToString(<ErrorPageWithoutStyle error={err} />)}
      </Html>
    )
    res.status(err.status || 500)
    res.send(`<!doctype html>${html}`)
  })
  
  //
  // Launch the server
  // -----------------------------------------------------------------------------
  // const promise = models.sync().catch(err => console.error(err.stack))
  if (!module.hot) {
    // promise.then(() => {
    app.listen(config.port, () => {
      console.info(`The server is running at http://localhost:${config.port}/`)
    })
    // })
  }
  
  //
  // Hot Module Replacement
  // -----------------------------------------------------------------------------
  if (module.hot) {
    app.hot = module.hot
    module.hot.accept('./router')
  }
  
  export default app
  