UNPKG

subapp-server

Version:

Electrode SubApp app server support

203 lines (168 loc) 5.6 kB
"use strict"; // Copy from electrode-react-webapp for now /* eslint-disable max-statements, complexity, global-require, no-magic-numbers, no-console */ const _ = require("lodash"); const Fs = require("fs"); const Path = require("path"); const util = require("util"); const Boom = require("@hapi/boom"); const HttpStatus = require("./http-status"); const readFile = util.promisify(Fs.readFile); const xaa = require("xaa"); const subAppUtil = require("subapp-util"); const registerRoutes = require("./register-routes"); const routesFromFile = require("./routes-from-file"); const routesFromDir = require("./routes-from-dir"); const { errorResponse, getSrcDir } = require("./utils"); async function handleFavIcon(server, options) { // // favicon handling, turn off by setting options.favicon to false // if (options.favicon === false) { return; } // look in CWD/static const iconFile = Path.resolve(options.favicon || "static/favicon.ico"); const icon = await xaa.try(() => readFile(iconFile)); server.route({ method: "get", path: "/favicon.ico", async handler() { if (icon) return icon; else return Boom.notFound(); } }); } async function registerRoutesFromFile({ server, srcDir, routes, topOpts }) { const subApps = await subAppUtil.scanSubAppsFromDir(srcDir); const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps); // setup for initialize callback server.ext("onPreAuth", async (request, h) => { const rte = routes[request.route.path]; if (rte && rte.initialize) { const x = await rte.initialize(request, h); if (x) return x; } return h.continue; }); // register routes for (const path in routes) { const route = routes[path]; const routeOptions = Object.assign({}, topOpts, route, { path }); routeOptions.uiConfig = _.get(server, "settings.app.config.ui", {}); // setup the template for rendering the route const routeRenderer = routesFromFile.setupRouteTemplate({ server, subAppsByPath, srcDir, routeOptions }); const useStream = routeOptions.useStream !== false; const handler = async (request, h) => { try { const context = await routeRenderer({ useStream, mode: "", request }); const data = context.result; const status = data.status; if (data instanceof Error) { // rethrow to get default error behavior below with helpful errors in dev mode throw data; } else if (status === undefined) { return h.response(data).type("text/html; charset=UTF-8").code(200); } else if (HttpStatus.redirect[status]) { return h.redirect(data.path).code(status); } else if (HttpStatus.displayHtml[status]) { return h.response(data.html !== undefined ? data.html : data).code(status); } else if (status >= 200 && status < 300) { return data.html !== undefined ? data.html : data; } else { return h.response(data).code(status); } } catch (err) { return errorResponse({ routeName: path, request, h, err }); } }; const defaultMethods = [].concat(route.methods || "get"); const paths = _.uniq([path].concat(route.paths).filter(x => x)).map(x => { if (typeof x === "string") { return { [x]: defaultMethods }; } return x; }); paths.forEach(pathObj => { _.each(pathObj, (method, xpath) => { server.route( Object.assign({}, route.settings, { path: xpath, method, handler }) ); }); }); } } async function setupRoutesFromFile(srcDir, server, pluginOpts) { const { routes, topOpts } = routesFromFile.searchRoutes(srcDir, pluginOpts); await handleFavIcon(server, topOpts); // invoke setup callback for (const path in routes) { if (routes[path].setup) { await routes[path].setup(server); } } await registerRoutesFromFile({ server, routes, topOpts, srcDir }); } async function setupRoutesFromDir(server, fromDir) { const { routes, topOpts } = fromDir; topOpts.routes = _.merge({}, routes, topOpts.routes); const routesWithSetup = routes.filter(x => x.route.setup); for (const route of routesWithSetup) { await route.route.setup(server); } await handleFavIcon(server, topOpts); // register onRequest const routesWithInit = routes.filter(x => x.route.initialize); if (routesWithInit.length > 0) { server.ext("onPreAuth", async (request, h) => { const rte = routes[request.route.path]; if (rte && rte.initialize) { const x = await rte.initialize(request, h); if (x) return x; } return h.continue; }); } // in case needed, add full protocol/host/port to dev bundle base URL topOpts.devBundleBase = subAppUtil.formUrl({ ..._.pick(topOpts.devServer, ["protocol", "host", "port"]), path: topOpts.devBundleBase }); registerRoutes({ routes, topOpts, server }); } async function setupSubAppHapiRoutes(server, pluginOpts) { const srcDir = getSrcDir(pluginOpts); const fromDir = await routesFromDir.searchRoutes(srcDir, pluginOpts); if (fromDir) { return await setupRoutesFromDir(server, fromDir); } // no directory based routes, then they must in a JS file return await xaa.wrap(() => setupRoutesFromFile(srcDir, server, pluginOpts)); } module.exports = { setupRoutesFromFile, setupSubAppHapiRoutes };